Spaces:
Running
Running
Upload 69 files
Browse files- background_service.py +5 -5
- dashboard.py +3 -3
- external_server.py +94 -21
- history.json +8 -0
- live_streaming.py +28 -7
- load_balancer.py +264 -64
- main.py +1 -24
- main.spec +44 -38
- node_client.py +138 -17
- offload_port.txt +1 -0
- peer_discovery.py +181 -32
- processor_manager.py +2 -2
- quick_connection_test.py +184 -27
- ram_manager.py +303 -99
- requirements.txt +3 -3
- rpc_server.py +61 -13
- run_task.py +182 -13
- simple_history.json +10 -0
- system_tray.py +235 -90
background_service.py
CHANGED
|
@@ -239,8 +239,8 @@ class BackgroundService:
|
|
| 239 |
self.setup_signal_handlers()
|
| 240 |
|
| 241 |
# تشغيل خادم HTTP API للتحكم
|
| 242 |
-
self.logger.info("🌐 تشغيل HTTP API على المنفذ
|
| 243 |
-
self.app.run(host='0.0.0.0', port=
|
| 244 |
|
| 245 |
def main():
|
| 246 |
service = BackgroundService()
|
|
@@ -254,7 +254,7 @@ def main():
|
|
| 254 |
# فحص حالة الخدمة
|
| 255 |
import requests
|
| 256 |
try:
|
| 257 |
-
response = requests.get('http://localhost:
|
| 258 |
print(json.dumps(response.json(), indent=2, ensure_ascii=False))
|
| 259 |
except:
|
| 260 |
print("❌ الخدمة غير متاحة")
|
|
@@ -262,7 +262,7 @@ def main():
|
|
| 262 |
# إيقاف الخدمة
|
| 263 |
import requests
|
| 264 |
try:
|
| 265 |
-
response = requests.post('http://localhost:
|
| 266 |
print(response.json()['message'])
|
| 267 |
except:
|
| 268 |
print("❌ فشل في إيقاف الخدمة")
|
|
@@ -270,7 +270,7 @@ def main():
|
|
| 270 |
# إظهار الواجهة التفاعلية
|
| 271 |
import requests
|
| 272 |
try:
|
| 273 |
-
response = requests.post('http://localhost:
|
| 274 |
print(response.json()['message'])
|
| 275 |
except:
|
| 276 |
print("❌ فشل في إظهار الواجهة التفاعلية")
|
|
|
|
| 239 |
self.setup_signal_handlers()
|
| 240 |
|
| 241 |
# تشغيل خادم HTTP API للتحكم
|
| 242 |
+
self.logger.info("🌐 تشغيل HTTP API على المنفذ51738")
|
| 243 |
+
self.app.run(host='0.0.0.0', port=5173, debug=False)
|
| 244 |
|
| 245 |
def main():
|
| 246 |
service = BackgroundService()
|
|
|
|
| 254 |
# فحص حالة الخدمة
|
| 255 |
import requests
|
| 256 |
try:
|
| 257 |
+
response = requests.get('http://localhost:5173/status')
|
| 258 |
print(json.dumps(response.json(), indent=2, ensure_ascii=False))
|
| 259 |
except:
|
| 260 |
print("❌ الخدمة غير متاحة")
|
|
|
|
| 262 |
# إيقاف الخدمة
|
| 263 |
import requests
|
| 264 |
try:
|
| 265 |
+
response = requests.post('http://localhost:5173/stop')
|
| 266 |
print(response.json()['message'])
|
| 267 |
except:
|
| 268 |
print("❌ فشل في إيقاف الخدمة")
|
|
|
|
| 270 |
# إظهار الواجهة التفاعلية
|
| 271 |
import requests
|
| 272 |
try:
|
| 273 |
+
response = requests.post('http://localhost:5173/show-ui')
|
| 274 |
print(response.json()['message'])
|
| 275 |
except:
|
| 276 |
print("❌ فشل في إظهار الواجهة التفاعلية")
|
dashboard.py
CHANGED
|
@@ -34,7 +34,7 @@ def get_system_status():
|
|
| 34 |
def broadcast_status():
|
| 35 |
while True:
|
| 36 |
status = get_system_status()
|
| 37 |
-
socketio.emit("status_update", {node_id: status}
|
| 38 |
socketio.sleep(5)
|
| 39 |
|
| 40 |
# ─────────────── صفحة الواجهة ───────────────
|
|
@@ -46,7 +46,7 @@ def index():
|
|
| 46 |
@socketio.on("status_update")
|
| 47 |
def handle_status_update(data):
|
| 48 |
connected_peers_data.update(data)
|
| 49 |
-
emit("update_peers", connected_peers_data, broadcast=True)
|
| 50 |
|
| 51 |
# ─────────────── دردشة ───────────────
|
| 52 |
@socketio.on("send_message")
|
|
@@ -55,6 +55,6 @@ def handle_message(data):
|
|
| 55 |
|
| 56 |
# ─────────────── تشغيل ───────────────
|
| 57 |
if __name__ == "__main__":
|
| 58 |
-
threading.Thread(target=broadcast_status).start()
|
| 59 |
logging.info(f"🚀 تشغيل Dashboard على {node_id}")
|
| 60 |
socketio.run(app, host="0.0.0.0", port=7000)
|
|
|
|
| 34 |
def broadcast_status():
|
| 35 |
while True:
|
| 36 |
status = get_system_status()
|
| 37 |
+
socketio.emit("status_update", {node_id: status}) # تم إزالة broadcast=True
|
| 38 |
socketio.sleep(5)
|
| 39 |
|
| 40 |
# ─────────────── صفحة الواجهة ───────────────
|
|
|
|
| 46 |
@socketio.on("status_update")
|
| 47 |
def handle_status_update(data):
|
| 48 |
connected_peers_data.update(data)
|
| 49 |
+
emit("update_peers", connected_peers_data, broadcast=True) # هذا صحيح لأنه داخل معالج حدث
|
| 50 |
|
| 51 |
# ─────────────── دردشة ───────────────
|
| 52 |
@socketio.on("send_message")
|
|
|
|
| 55 |
|
| 56 |
# ─────────────── تشغيل ───────────────
|
| 57 |
if __name__ == "__main__":
|
| 58 |
+
threading.Thread(target=broadcast_status, daemon=True).start() # أضفت daemon=True
|
| 59 |
logging.info(f"🚀 تشغيل Dashboard على {node_id}")
|
| 60 |
socketio.run(app, host="0.0.0.0", port=7000)
|
external_server.py
CHANGED
|
@@ -4,6 +4,7 @@ external_server.py — سيرفر مركزي لتوزيع المهام + Dashboa
|
|
| 4 |
"""
|
| 5 |
import logging
|
| 6 |
import requests
|
|
|
|
| 7 |
from flask import Flask, request, jsonify, render_template
|
| 8 |
from flask_cors import CORS
|
| 9 |
from flask_socketio import SocketIO, emit
|
|
@@ -18,29 +19,49 @@ socketio = SocketIO(app, cors_allowed_origins="*")
|
|
| 18 |
|
| 19 |
connected_peers = {} # {node_id: {"cpu":%, "ram":%, "gpu":%}}
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
# ─────────────── اختيار أفضل Peer ───────────────
|
| 22 |
def select_best_peer():
|
| 23 |
-
|
| 24 |
-
if not peers_list:
|
| 25 |
logging.warning("⚠️ لا توجد أجهزة مسجلة حالياً.")
|
| 26 |
return None
|
| 27 |
|
| 28 |
try:
|
| 29 |
peer_loads = []
|
| 30 |
-
for peer_url in
|
| 31 |
try:
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
if resp.ok:
|
| 34 |
data = resp.json()
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
continue
|
| 38 |
|
| 39 |
if not peer_loads:
|
| 40 |
return None
|
| 41 |
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
| 44 |
except Exception as e:
|
| 45 |
logging.error(f"❌ خطأ في اختيار الـ Peer: {e}")
|
| 46 |
return None
|
|
@@ -49,21 +70,34 @@ def select_best_peer():
|
|
| 49 |
@app.route("/submit_task", methods=["POST"])
|
| 50 |
def submit_task():
|
| 51 |
data = request.get_json()
|
| 52 |
-
if not data or "
|
| 53 |
-
return jsonify({"error": "يجب تحديد
|
| 54 |
|
| 55 |
peer = select_best_peer()
|
| 56 |
if not peer:
|
| 57 |
return jsonify({"error": "لا توجد أجهزة متاحة حالياً"}), 503
|
| 58 |
|
| 59 |
try:
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
if resp.ok:
|
| 62 |
-
|
|
|
|
|
|
|
| 63 |
else:
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
except Exception as e:
|
| 66 |
-
logging.error(f"❌ خطأ في إرسال
|
| 67 |
return jsonify({"error": str(e)}), 500
|
| 68 |
|
| 69 |
# ─────────────── API تحديث حالة الأجهزة ───────────────
|
|
@@ -75,24 +109,63 @@ def update_status():
|
|
| 75 |
return jsonify({"error": "node_id مطلوب"}), 400
|
| 76 |
|
| 77 |
connected_peers[node_id] = {
|
| 78 |
-
"cpu": data.get("cpu"),
|
| 79 |
-
"ram": data.get("ram"),
|
| 80 |
-
"gpu": data.get("gpu")
|
|
|
|
| 81 |
}
|
|
|
|
| 82 |
socketio.emit("update_peers", connected_peers, broadcast=True)
|
|
|
|
| 83 |
return jsonify({"status": "ok"})
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
# ─────────────── صفحة Dashboard ───────────────
|
| 86 |
@app.route("/")
|
| 87 |
def index():
|
| 88 |
-
return render_template("dashboard.html")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
# ─────────────── دردشة ───────────────
|
| 91 |
@socketio.on("send_message")
|
| 92 |
def handle_message(data):
|
| 93 |
-
|
| 94 |
|
| 95 |
# ─────────────── تشغيل السيرفر ───────────────
|
| 96 |
if __name__ == "__main__":
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
"""
|
| 5 |
import logging
|
| 6 |
import requests
|
| 7 |
+
import socket
|
| 8 |
from flask import Flask, request, jsonify, render_template
|
| 9 |
from flask_cors import CORS
|
| 10 |
from flask_socketio import SocketIO, emit
|
|
|
|
| 19 |
|
| 20 |
connected_peers = {} # {node_id: {"cpu":%, "ram":%, "gpu":%}}
|
| 21 |
|
| 22 |
+
# ─────────────── التحقق من توفر المنفذ ───────────────
|
| 23 |
+
def is_port_available(port):
|
| 24 |
+
try:
|
| 25 |
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
| 26 |
+
s.bind(('0.0.0.0', port))
|
| 27 |
+
return True
|
| 28 |
+
except OSError:
|
| 29 |
+
return False
|
| 30 |
+
|
| 31 |
# ─────────────── اختيار أفضل Peer ───────────────
|
| 32 |
def select_best_peer():
|
| 33 |
+
if not PEERS:
|
|
|
|
| 34 |
logging.warning("⚠️ لا توجد أجهزة مسجلة حالياً.")
|
| 35 |
return None
|
| 36 |
|
| 37 |
try:
|
| 38 |
peer_loads = []
|
| 39 |
+
for peer_url in PEERS:
|
| 40 |
try:
|
| 41 |
+
# بناء URL لفحص الحالة
|
| 42 |
+
status_url = peer_url.replace('/run_task', '/health')
|
| 43 |
+
if '/run_task' not in peer_url:
|
| 44 |
+
status_url = f"{peer_url}/health"
|
| 45 |
+
|
| 46 |
+
resp = requests.get(status_url, timeout=2)
|
| 47 |
if resp.ok:
|
| 48 |
data = resp.json()
|
| 49 |
+
# افتراض أن الخادم يعيد cpu_load أو استخدام قيمة افتراضية
|
| 50 |
+
cpu_load = data.get("cpu_load", 50)
|
| 51 |
+
peer_loads.append((peer_url, cpu_load))
|
| 52 |
+
logging.info(f"✅ {peer_url} - الحمل: {cpu_load}%")
|
| 53 |
+
except Exception as e:
|
| 54 |
+
logging.warning(f"❌ لا يمكن الوصول إلى {peer_url}: {e}")
|
| 55 |
continue
|
| 56 |
|
| 57 |
if not peer_loads:
|
| 58 |
return None
|
| 59 |
|
| 60 |
+
# اختيار الأقل حملًا
|
| 61 |
+
best_peer = min(peer_loads, key=lambda x: x[1])
|
| 62 |
+
logging.info(f"🎯 أفضل جهاز: {best_peer[0]} مع حمل {best_peer[1]}%")
|
| 63 |
+
return best_peer[0]
|
| 64 |
+
|
| 65 |
except Exception as e:
|
| 66 |
logging.error(f"❌ خطأ في اختيار الـ Peer: {e}")
|
| 67 |
return None
|
|
|
|
| 70 |
@app.route("/submit_task", methods=["POST"])
|
| 71 |
def submit_task():
|
| 72 |
data = request.get_json()
|
| 73 |
+
if not data or "func" not in data:
|
| 74 |
+
return jsonify({"error": "يجب تحديد اسم الدالة (func)"}), 400
|
| 75 |
|
| 76 |
peer = select_best_peer()
|
| 77 |
if not peer:
|
| 78 |
return jsonify({"error": "لا توجد أجهزة متاحة حالياً"}), 503
|
| 79 |
|
| 80 |
try:
|
| 81 |
+
# تأكد من أن عنوان الـ Peer صحيح
|
| 82 |
+
if not peer.startswith('http'):
|
| 83 |
+
peer = f"http://{peer}"
|
| 84 |
+
|
| 85 |
+
logging.info(f"📤 إرسال المهمة إلى: {peer}")
|
| 86 |
+
resp = requests.post(peer, json=data, timeout=30)
|
| 87 |
+
|
| 88 |
if resp.ok:
|
| 89 |
+
result = resp.json()
|
| 90 |
+
logging.info(f"✅ تم تنفيذ المهمة بنجاح على {peer}")
|
| 91 |
+
return jsonify({"status": "success", "result": result, "executed_on": peer})
|
| 92 |
else:
|
| 93 |
+
logging.error(f"❌ فشل تنفيذ المهمة على {peer}: {resp.status_code}")
|
| 94 |
+
return jsonify({"error": f"فشل إرسال المهمة: {resp.text}"}), 500
|
| 95 |
+
|
| 96 |
+
except requests.exceptions.Timeout:
|
| 97 |
+
logging.error(f"⏰ انتهت المهلة أثناء الاتصال بـ {peer}")
|
| 98 |
+
return jsonify({"error": "انتهت مهلة الاتصال"}), 408
|
| 99 |
except Exception as e:
|
| 100 |
+
logging.error(f"❌ خطأ في إرسال المهمة إلى {peer}: {e}")
|
| 101 |
return jsonify({"error": str(e)}), 500
|
| 102 |
|
| 103 |
# ─────────────── API تحديث حالة الأجهزة ───────────────
|
|
|
|
| 109 |
return jsonify({"error": "node_id مطلوب"}), 400
|
| 110 |
|
| 111 |
connected_peers[node_id] = {
|
| 112 |
+
"cpu": data.get("cpu", 0),
|
| 113 |
+
"ram": data.get("ram", 0),
|
| 114 |
+
"gpu": data.get("gpu", 0),
|
| 115 |
+
"last_update": "now" # يمكن إضافة timestamp
|
| 116 |
}
|
| 117 |
+
|
| 118 |
socketio.emit("update_peers", connected_peers, broadcast=True)
|
| 119 |
+
logging.info(f"📊 تم تحديث حالة {node_id}")
|
| 120 |
return jsonify({"status": "ok"})
|
| 121 |
|
| 122 |
+
# ─────────────── صفحة الحالة ───────────────
|
| 123 |
+
@app.route("/status")
|
| 124 |
+
def status():
|
| 125 |
+
return jsonify({
|
| 126 |
+
"connected_peers": connected_peers,
|
| 127 |
+
"available_peers": list(PEERS),
|
| 128 |
+
"total_peers": len(PEERS)
|
| 129 |
+
})
|
| 130 |
+
|
| 131 |
# ─────────────── صفحة Dashboard ───────────────
|
| 132 |
@app.route("/")
|
| 133 |
def index():
|
| 134 |
+
return render_template("dashboard.html", peers=connected_peers)
|
| 135 |
+
|
| 136 |
+
# ─────────────── استقبال تحديثات الحالة عبر WebSocket ───────────────
|
| 137 |
+
@socketio.on("status_update")
|
| 138 |
+
def handle_status_update(data):
|
| 139 |
+
connected_peers.update(data)
|
| 140 |
+
emit("update_peers", connected_peers, broadcast=True)
|
| 141 |
|
| 142 |
# ─────────────── دردشة ───────────────
|
| 143 |
@socketio.on("send_message")
|
| 144 |
def handle_message(data):
|
| 145 |
+
emit("receive_message", data, broadcast=True)
|
| 146 |
|
| 147 |
# ─────────────── تشغيل السيرفر ───────────────
|
| 148 |
if __name__ == "__main__":
|
| 149 |
+
# محاولة منافذ مختلفة
|
| 150 |
+
ports_to_try = [5005, 5006, 5007, 5008, 5009]
|
| 151 |
+
selected_port = None
|
| 152 |
+
|
| 153 |
+
for port in ports_to_try:
|
| 154 |
+
if is_port_available(port):
|
| 155 |
+
selected_port = port
|
| 156 |
+
break
|
| 157 |
+
|
| 158 |
+
if selected_port is None:
|
| 159 |
+
logging.error("❌ لا توجد منافذ متاحة. حاول إغلاق التطبيقات الأخرى.")
|
| 160 |
+
exit(1)
|
| 161 |
+
|
| 162 |
+
logging.info(f"🚀 بدء السيرفر المركزي مع Dashboard على المنفذ {selected_port}")
|
| 163 |
+
|
| 164 |
+
try:
|
| 165 |
+
socketio.run(app, host="0.0.0.0", port=selected_port, debug=False)
|
| 166 |
+
except OSError as e:
|
| 167 |
+
logging.error(f"❌ فشل تشغيل السيرفر على المنفذ {selected_port}: {e}")
|
| 168 |
+
except KeyboardInterrupt:
|
| 169 |
+
logging.info("⏹️ إيقاف السيرفر...")
|
| 170 |
+
except Exception as e:
|
| 171 |
+
logging.error(f"❌ خطأ غير متوقع: {e}")
|
history.json
CHANGED
|
@@ -19,6 +19,14 @@
|
|
| 19 |
"role": "user",
|
| 20 |
"content": "مم"
|
| 21 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
{
|
| 23 |
"role": "assistant",
|
| 24 |
"content": "عذراً، حدث خطأ: 401"
|
|
|
|
| 19 |
"role": "user",
|
| 20 |
"content": "مم"
|
| 21 |
},
|
| 22 |
+
{
|
| 23 |
+
"role": "assistant",
|
| 24 |
+
"content": "عذراً، حدث خطأ: 401"
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"role": "user",
|
| 28 |
+
"content": "hi"
|
| 29 |
+
},
|
| 30 |
{
|
| 31 |
"role": "assistant",
|
| 32 |
"content": "عذراً، حدث خطأ: 401"
|
live_streaming.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
|
| 2 |
# live_streaming.py - نظام البث المباشر للألعاب والفيديو
|
| 3 |
|
| 4 |
import cv2
|
|
@@ -61,13 +60,35 @@ def stream_offload(func):
|
|
| 61 |
def estimate_stream_complexity(func, args, kwargs):
|
| 62 |
"""تقدير تعقيد معالجة البث"""
|
| 63 |
if func.__name__ == "process_game_stream":
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
elif func.__name__ == "real_time_video_enhancement":
|
| 66 |
-
|
|
|
|
|
|
|
| 67 |
elif func.__name__ == "multi_stream_processing":
|
| 68 |
-
|
|
|
|
|
|
|
| 69 |
elif func.__name__ == "ai_commentary_generation":
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
return 40
|
| 72 |
|
| 73 |
# ═══════════════════════════════════════════════════════════════
|
|
@@ -75,7 +96,7 @@ def estimate_stream_complexity(func, args, kwargs):
|
|
| 75 |
# ═══════════════════════════════════════════════════════════════
|
| 76 |
|
| 77 |
@stream_offload
|
| 78 |
-
def process_game_stream(stream_data, fps, resolution, enhancements=None):
|
| 79 |
"""معالجة بث الألعاب في الوقت الفعلي"""
|
| 80 |
start_time = time.time()
|
| 81 |
|
|
@@ -221,7 +242,7 @@ def multi_stream_processing(streams_data, processing_mode="parallel"):
|
|
| 221 |
# ═══════════════════════════════════════════════════════════════
|
| 222 |
|
| 223 |
@stream_offload
|
| 224 |
-
def ai_commentary_generation(game_events, commentary_length, language="ar"):
|
| 225 |
"""توليد تعليق ذكي للألعاب"""
|
| 226 |
start_time = time.time()
|
| 227 |
|
|
|
|
|
|
|
| 1 |
# live_streaming.py - نظام البث المباشر للألعاب والفيديو
|
| 2 |
|
| 3 |
import cv2
|
|
|
|
| 60 |
def estimate_stream_complexity(func, args, kwargs):
|
| 61 |
"""تقدير تعقيد معالجة البث"""
|
| 62 |
if func.__name__ == "process_game_stream":
|
| 63 |
+
# استخراج القيم الرقمية من الدقة
|
| 64 |
+
resolution = args[2] if len(args) > 2 else "1920x1080"
|
| 65 |
+
if isinstance(resolution, str) and 'x' in resolution:
|
| 66 |
+
try:
|
| 67 |
+
width, height = map(int, resolution.split('x'))
|
| 68 |
+
resolution_factor = width * height / 10000
|
| 69 |
+
except:
|
| 70 |
+
resolution_factor = 1920 * 1080 / 10000 # قيمة افتراضية
|
| 71 |
+
else:
|
| 72 |
+
resolution_factor = 1920 * 1080 / 10000
|
| 73 |
+
|
| 74 |
+
fps = args[1] if len(args) > 1 else 60
|
| 75 |
+
return fps * resolution_factor
|
| 76 |
+
|
| 77 |
elif func.__name__ == "real_time_video_enhancement":
|
| 78 |
+
enhancements = args[0] if len(args) > 0 else []
|
| 79 |
+
return len(enhancements) * 20 # عدد التحسينات × 20
|
| 80 |
+
|
| 81 |
elif func.__name__ == "multi_stream_processing":
|
| 82 |
+
streams = args[0] if len(args) > 0 else []
|
| 83 |
+
return len(streams) * 25 # عدد البثوث × 25
|
| 84 |
+
|
| 85 |
elif func.__name__ == "ai_commentary_generation":
|
| 86 |
+
commentary_length = args[1] if len(args) > 1 else 50
|
| 87 |
+
return commentary_length * 15 # طول النص × 15
|
| 88 |
+
|
| 89 |
+
elif func.__name__ == "stream_quality_optimization":
|
| 90 |
+
return 40 # تعقيد متوسط
|
| 91 |
+
|
| 92 |
return 40
|
| 93 |
|
| 94 |
# ═══════════════════════════════════════════════════════════════
|
|
|
|
| 96 |
# ═══════════════════════════════════════════════════════════════
|
| 97 |
|
| 98 |
@stream_offload
|
| 99 |
+
def process_game_stream(stream_data, fps=60, resolution="1920x1080", enhancements=None):
|
| 100 |
"""معالجة بث الألعاب في الوقت الفعلي"""
|
| 101 |
start_time = time.time()
|
| 102 |
|
|
|
|
| 242 |
# ═══════════════════════════════════════════════════════════════
|
| 243 |
|
| 244 |
@stream_offload
|
| 245 |
+
def ai_commentary_generation(game_events, commentary_length=50, language="ar"):
|
| 246 |
"""توليد تعليق ذكي للألعاب"""
|
| 247 |
start_time = time.time()
|
| 248 |
|
load_balancer.py
CHANGED
|
@@ -1,78 +1,278 @@
|
|
| 1 |
# load_balancer.py
|
| 2 |
-
import peer_discovery
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
from peer_discovery import PORT
|
| 4 |
|
| 5 |
-
|
| 6 |
-
try:
|
| 7 |
-
r = requests.post(peer, json={"func": func,
|
| 8 |
-
"args": list(args),
|
| 9 |
-
"kwargs": kw}, timeout=12)
|
| 10 |
-
return r.json()
|
| 11 |
-
except Exception as e:
|
| 12 |
-
return {"error": str(e)}
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
try:
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
return (
|
| 55 |
-
ip.startswith('192.168.') or
|
| 56 |
-
ip.startswith('10.') or
|
| 57 |
-
ip.startswith('172.') or
|
| 58 |
-
ip == '127.0.0.1'
|
| 59 |
-
)
|
| 60 |
|
| 61 |
-
def
|
| 62 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
try:
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
if peer:
|
| 72 |
-
print(f"\n🛰️ إرسال إلى {peer}")
|
| 73 |
-
res = send(peer, "prime_calculation", 30000)
|
| 74 |
-
else:
|
| 75 |
-
print("\n⚙️ لا أقران؛ العمل محليّ على", socket.gethostname())
|
| 76 |
-
res = smart_tasks.prime_calculation(30000)
|
| 77 |
-
print("🔹 النتيجة (جزئية):", str(res)[:120])
|
| 78 |
-
time.sleep(10)
|
|
|
|
| 1 |
# load_balancer.py
|
| 2 |
+
import peer_discovery
|
| 3 |
+
import requests
|
| 4 |
+
import time
|
| 5 |
+
import smart_tasks
|
| 6 |
+
import psutil
|
| 7 |
+
import socket
|
| 8 |
+
import threading
|
| 9 |
+
import logging
|
| 10 |
+
from datetime import datetime
|
| 11 |
from peer_discovery import PORT
|
| 12 |
|
| 13 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
+
class SmartLoadBalancer:
|
| 16 |
+
def __init__(self):
|
| 17 |
+
self.peer_stats = {}
|
| 18 |
+
self.local_hostname = socket.gethostname()
|
| 19 |
+
self.local_ip = self.get_local_ip()
|
| 20 |
+
self.running = True
|
| 21 |
+
|
| 22 |
+
def get_local_ip(self):
|
| 23 |
+
"""الحصول على IP المحلي"""
|
| 24 |
+
try:
|
| 25 |
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
| 26 |
+
s.connect(("8.8.8.8", 80))
|
| 27 |
+
ip = s.getsockname()[0]
|
| 28 |
+
s.close()
|
| 29 |
+
return ip
|
| 30 |
+
except:
|
| 31 |
+
return "127.0.0.1"
|
| 32 |
|
| 33 |
+
def send(self, peer, func, *args, **kwargs):
|
| 34 |
+
"""إرسال مهمة إلى نظير"""
|
| 35 |
+
try:
|
| 36 |
+
url = f"{peer}/run" if not peer.endswith("/run") else peer
|
| 37 |
+
payload = {
|
| 38 |
+
"func": func,
|
| 39 |
+
"args": list(args),
|
| 40 |
+
"kwargs": kwargs
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
logging.info(f"📤 إرسال مهمة {func} إلى {peer}")
|
| 44 |
+
response = requests.post(url, json=payload, timeout=15)
|
| 45 |
+
|
| 46 |
+
if response.status_code == 200:
|
| 47 |
+
result = response.json()
|
| 48 |
+
logging.info(f"✅ تم استلام النتيجة من {peer}")
|
| 49 |
+
return result
|
| 50 |
+
else:
|
| 51 |
+
logging.error(f"❌ خطأ من {peer}: {response.status_code}")
|
| 52 |
+
return {"error": f"HTTP {response.status_code}"}
|
| 53 |
+
|
| 54 |
+
except requests.exceptions.Timeout:
|
| 55 |
+
logging.error(f"⏰ انتهت المهلة مع {peer}")
|
| 56 |
+
return {"error": "Request timeout"}
|
| 57 |
+
except requests.exceptions.ConnectionError:
|
| 58 |
+
logging.error(f"🔌 تعذر الاتصال بـ {peer}")
|
| 59 |
+
return {"error": "Connection failed"}
|
| 60 |
+
except Exception as e:
|
| 61 |
+
logging.error(f"❌ خطأ غير متوقع مع {peer}: {str(e)}")
|
| 62 |
+
return {"error": str(e)}
|
| 63 |
|
| 64 |
+
def choose_peer(self):
|
| 65 |
+
"""اختيار أفضل جهاز مع خوارزمية متطورة"""
|
| 66 |
+
available_peers = self.get_available_peers()
|
| 67 |
+
|
| 68 |
+
if not available_peers:
|
| 69 |
+
logging.info("🔍 لم يتم العثور على أقران متاحة")
|
| 70 |
+
return None
|
| 71 |
+
|
| 72 |
+
# تقييم كل نظير
|
| 73 |
+
peer_scores = []
|
| 74 |
+
for peer in available_peers:
|
| 75 |
+
score = self.evaluate_peer(peer)
|
| 76 |
+
if score > 0:
|
| 77 |
+
peer_scores.append((peer, score))
|
| 78 |
+
|
| 79 |
+
if not peer_scores:
|
| 80 |
+
return None
|
| 81 |
+
|
| 82 |
+
# اختيار النظير بأعلى درجة
|
| 83 |
+
best_peer = max(peer_scores, key=lambda x: x[1])[0]
|
| 84 |
+
logging.info(f"🏆 أفضل نظير مختار: {best_peer}")
|
| 85 |
+
return best_peer
|
| 86 |
|
| 87 |
+
def get_available_peers(self):
|
| 88 |
+
"""الحصول على قائمة الأقران المتاحة"""
|
| 89 |
+
peers = []
|
| 90 |
+
|
| 91 |
+
# اكتشاف الأقران المحليين
|
| 92 |
+
local_peers = self.discover_local_peers()
|
| 93 |
+
peers.extend(local_peers)
|
| 94 |
+
|
| 95 |
+
# إضافة الأقران من peer_discovery
|
| 96 |
+
for peer in list(peer_discovery.PEERS):
|
| 97 |
+
if peer not in peers:
|
| 98 |
+
peers.append(peer)
|
| 99 |
+
|
| 100 |
+
# إزالة التكرارات والنظير المحلي
|
| 101 |
+
unique_peers = []
|
| 102 |
+
for peer in peers:
|
| 103 |
+
if self.is_self_peer(peer):
|
| 104 |
+
continue
|
| 105 |
+
if peer not in unique_peers:
|
| 106 |
+
unique_peers.append(peer)
|
| 107 |
+
|
| 108 |
+
logging.info(f"👥 الأقران المتاحون: {len(unique_peers)}")
|
| 109 |
+
return unique_peers
|
| 110 |
|
| 111 |
+
def discover_local_peers(self):
|
| 112 |
+
"""اكتشاف الأقران على الشبكة المحلية"""
|
| 113 |
+
local_peers = []
|
| 114 |
+
base_ip = ".".join(self.local_ip.split(".")[:-1]) # الحصول على 192.168.1
|
| 115 |
+
|
| 116 |
+
# فحص نطاق IPs المحلي
|
| 117 |
+
for i in range(1, 255):
|
| 118 |
+
if i == int(self.local_ip.split(".")[-1]):
|
| 119 |
+
continue # تخطي الذات
|
| 120 |
+
|
| 121 |
+
ip = f"{base_ip}.{i}"
|
| 122 |
+
peer_url = f"http://{ip}:{PORT}"
|
| 123 |
+
|
| 124 |
+
# فحص سريع للتوصيل
|
| 125 |
+
if self.check_peer_availability(peer_url):
|
| 126 |
+
local_peers.append(peer_url)
|
| 127 |
+
|
| 128 |
+
return local_peers
|
| 129 |
|
| 130 |
+
def check_peer_availability(self, peer_url):
|
| 131 |
+
"""فحص توفر النظير"""
|
| 132 |
+
try:
|
| 133 |
+
response = requests.get(f"{peer_url}/status", timeout=2)
|
| 134 |
+
return response.status_code == 200
|
| 135 |
+
except:
|
| 136 |
+
return False
|
| 137 |
+
|
| 138 |
+
def is_self_peer(self, peer_url):
|
| 139 |
+
"""فحص إذا كان النظير هو الجهاز الحالي"""
|
| 140 |
+
if f"://{self.local_ip}:" in peer_url:
|
| 141 |
+
return True
|
| 142 |
+
if f"://localhost:" in peer_url:
|
| 143 |
+
return True
|
| 144 |
+
if f"://127.0.0.1:" in peer_url:
|
| 145 |
+
return True
|
| 146 |
+
return False
|
| 147 |
+
|
| 148 |
+
def evaluate_peer(self, peer):
|
| 149 |
+
"""تقييم أداء النظير"""
|
| 150 |
try:
|
| 151 |
+
# الحصول على حالة النظام
|
| 152 |
+
status_url = peer.replace("/run", "/status") if "/run" in peer else f"{peer}/status"
|
| 153 |
+
response = requests.get(status_url, timeout=3)
|
| 154 |
+
|
| 155 |
+
if response.status_code == 200:
|
| 156 |
+
status = response.json()
|
| 157 |
+
|
| 158 |
+
# حساب الدرجة
|
| 159 |
+
score = 100
|
| 160 |
+
|
| 161 |
+
# خصم حسب استخدام CPU
|
| 162 |
+
cpu_usage = status.get("cpu_usage", 50)
|
| 163 |
+
score -= cpu_usage * 0.5
|
| 164 |
+
|
| 165 |
+
# خصم حسب استخدام الذاكرة
|
| 166 |
+
memory_usage = status.get("memory_usage", 50)
|
| 167 |
+
score -= memory_usage * 0.3
|
| 168 |
+
|
| 169 |
+
# مكافأة للاتصالات المحلية
|
| 170 |
+
if self.is_local_peer(peer):
|
| 171 |
+
score += 20
|
| 172 |
+
|
| 173 |
+
# خصم للاتصال البطيء
|
| 174 |
+
response_time = response.elapsed.total_seconds()
|
| 175 |
+
score -= response_time * 10
|
| 176 |
+
|
| 177 |
+
return max(0, score)
|
| 178 |
+
|
| 179 |
+
except Exception as e:
|
| 180 |
+
logging.debug(f"تعذر تقييم {peer}: {str(e)}")
|
| 181 |
+
|
| 182 |
+
return 0
|
| 183 |
+
|
| 184 |
+
def is_local_peer(self, peer):
|
| 185 |
+
"""فحص إذا كان النظير محلي"""
|
| 186 |
+
peer_ip = peer.split("://")[1].split(":")[0] if "://" in peer else peer.split(":")[0]
|
| 187 |
+
return self.is_local_ip(peer_ip)
|
| 188 |
+
|
| 189 |
+
def is_local_ip(self, ip):
|
| 190 |
+
"""فحص إذا كان IP محلي"""
|
| 191 |
+
return (
|
| 192 |
+
ip.startswith('192.168.') or
|
| 193 |
+
ip.startswith('10.') or
|
| 194 |
+
ip.startswith('172.') or
|
| 195 |
+
ip in ['127.0.0.1', 'localhost', self.local_ip]
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
def execute_task(self, func_name, *args, **kwargs):
|
| 199 |
+
"""تنفيذ المهمة إما محلياً أو عن بعد"""
|
| 200 |
+
peer = self.choose_peer()
|
| 201 |
+
|
| 202 |
+
if peer:
|
| 203 |
+
logging.info(f"🛰️ إرسال مهمة {func_name} إلى {peer}")
|
| 204 |
+
result = self.send(peer, func_name, *args, **kwargs)
|
| 205 |
+
|
| 206 |
+
if "error" not in result:
|
| 207 |
+
return result
|
| 208 |
+
else:
|
| 209 |
+
logging.warning(f"❌ فشل الإرسال إلى {peer}: {result['error']}")
|
| 210 |
+
|
| 211 |
+
# التنفيذ المحلي كبديل
|
| 212 |
+
logging.info(f"⚙️ تنفيذ محلي للمهمة {func_name}")
|
| 213 |
+
return self.execute_locally(func_name, *args, **kwargs)
|
| 214 |
+
|
| 215 |
+
def execute_locally(self, func_name, *args, **kwargs):
|
| 216 |
+
"""تنفيذ المهمة محلياً"""
|
| 217 |
+
try:
|
| 218 |
+
if hasattr(smart_tasks, func_name):
|
| 219 |
+
func = getattr(smart_tasks, func_name)
|
| 220 |
+
result = func(*args, **kwargs)
|
| 221 |
+
return result
|
| 222 |
+
else:
|
| 223 |
+
return {"error": f"الدالة {func_name} غير موجودة"}
|
| 224 |
+
except Exception as e:
|
| 225 |
+
return {"error": str(e)}
|
| 226 |
+
|
| 227 |
+
def start_monitoring(self):
|
| 228 |
+
"""بدء مراقبة الأقران"""
|
| 229 |
+
def monitor_loop():
|
| 230 |
+
while self.running:
|
| 231 |
+
try:
|
| 232 |
+
available_peers = self.get_available_peers()
|
| 233 |
+
logging.info(f"📊 المراقبة: {len(available_peers)} أقران متاحون")
|
| 234 |
+
time.sleep(30)
|
| 235 |
+
except Exception as e:
|
| 236 |
+
logging.error(f"خطأ في المراقبة: {e}")
|
| 237 |
+
time.sleep(60)
|
| 238 |
+
|
| 239 |
+
monitor_thread = threading.Thread(target=monitor_loop, daemon=True)
|
| 240 |
+
monitor_thread.start()
|
| 241 |
+
|
| 242 |
+
def stop(self):
|
| 243 |
+
"""إيقاف موازن الحمل"""
|
| 244 |
+
self.running = False
|
| 245 |
|
| 246 |
+
# إنشاء نسخة عالمية
|
| 247 |
+
load_balancer = SmartLoadBalancer()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
|
| 249 |
+
def send_task(func_name, *args, **kwargs):
|
| 250 |
+
"""وظيفة مساعدة لإرسال المهام"""
|
| 251 |
+
return load_balancer.execute_task(func_name, *args, **kwargs)
|
| 252 |
+
|
| 253 |
+
# التشغيل الرئيسي
|
| 254 |
+
def main():
|
| 255 |
+
logging.info("🚀 بدء تشغيل موازن الحمل الذكي")
|
| 256 |
+
|
| 257 |
+
# بدء المراقبة
|
| 258 |
+
load_balancer.start_monitoring()
|
| 259 |
+
|
| 260 |
try:
|
| 261 |
+
while load_balancer.running:
|
| 262 |
+
# تنفيذ مهام اختبارية
|
| 263 |
+
result = load_balancer.execute_task("prime_calculation", 20000)
|
| 264 |
+
|
| 265 |
+
if "error" in result:
|
| 266 |
+
logging.error(f"❌ خطأ في التنفيذ: {result['error']}")
|
| 267 |
+
else:
|
| 268 |
+
primes_count = result.get('count', 0)
|
| 269 |
+
logging.info(f"✅ تم تنفيذ المهمة - عدد الأعداد الأولية: {primes_count}")
|
| 270 |
+
|
| 271 |
+
time.sleep(15)
|
| 272 |
+
|
| 273 |
+
except KeyboardInterrupt:
|
| 274 |
+
logging.info("🛑 إيقاف موازن الحمل...")
|
| 275 |
+
load_balancer.stop()
|
| 276 |
|
| 277 |
+
if __name__ == "__main__":
|
| 278 |
+
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
main.py
CHANGED
|
@@ -20,6 +20,7 @@ from flask import Flask, request, jsonify
|
|
| 20 |
from flask_cors import CORS
|
| 21 |
from peer_discovery import CENTRAL_REGISTRY_SERVERS
|
| 22 |
from peer_discovery import PORT
|
|
|
|
| 23 |
# ─────────────── إعدادات المسارات ───────────────
|
| 24 |
FILE = Path(__file__).resolve()
|
| 25 |
BASE_DIR = FILE.parent
|
|
@@ -38,30 +39,6 @@ logging.basicConfig(
|
|
| 38 |
]
|
| 39 |
)
|
| 40 |
|
| 41 |
-
import multiprocessing
|
| 42 |
-
import time
|
| 43 |
-
import sys
|
| 44 |
-
import os
|
| 45 |
-
|
| 46 |
-
# دالة تشغيل السيرفر الخارجي
|
| 47 |
-
def run_external_server():
|
| 48 |
-
"""تشغيل ملف exexternal_server.py في عملية منفصلة"""
|
| 49 |
-
try:
|
| 50 |
-
# استيراد وتشغيل الملف
|
| 51 |
-
import exexternal_server
|
| 52 |
-
exexternal_server.main() # افترض أن الدالة الرئيسية اسمها main
|
| 53 |
-
except Exception as e:
|
| 54 |
-
print(f"خطأ في تشغيل السيرفر: {e}")
|
| 55 |
-
|
| 56 |
-
# في الملف الرئيسي
|
| 57 |
-
def start_external_server_process():
|
| 58 |
-
"""تشغيل السيرفر في عملية منفصلة"""
|
| 59 |
-
process = multiprocessing.Process(target=run_external_server)
|
| 60 |
-
process.daemon = True # سيتوقف تلقائياً عند إنهاء البرنامج الرئيسي
|
| 61 |
-
process.start()
|
| 62 |
-
print(f"تم تشغيل السيرفر الخارجي (PID: {process.pid})")
|
| 63 |
-
return process
|
| 64 |
-
|
| 65 |
# ─────────────── تحميل متغيرات البيئة ───────────────
|
| 66 |
try:
|
| 67 |
from dotenv import load_dotenv
|
|
|
|
| 20 |
from flask_cors import CORS
|
| 21 |
from peer_discovery import CENTRAL_REGISTRY_SERVERS
|
| 22 |
from peer_discovery import PORT
|
| 23 |
+
import external_server
|
| 24 |
# ─────────────── إعدادات المسارات ───────────────
|
| 25 |
FILE = Path(__file__).resolve()
|
| 26 |
BASE_DIR = FILE.parent
|
|
|
|
| 39 |
]
|
| 40 |
)
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
# ─────────────── تحميل متغيرات البيئة ───────────────
|
| 43 |
try:
|
| 44 |
from dotenv import load_dotenv
|
main.spec
CHANGED
|
@@ -1,38 +1,44 @@
|
|
| 1 |
-
# -*- mode: python ; coding: utf-8 -*-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- mode: python ; coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
block_cipher = None
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
a = Analysis(
|
| 8 |
+
['main.py'],
|
| 9 |
+
pathex=[],
|
| 10 |
+
binaries=[],
|
| 11 |
+
datas=[('*', '.')],
|
| 12 |
+
hiddenimports=[],
|
| 13 |
+
hookspath=[],
|
| 14 |
+
hooksconfig={},
|
| 15 |
+
runtime_hooks=[],
|
| 16 |
+
excludes=[],
|
| 17 |
+
win_no_prefer_redirects=False,
|
| 18 |
+
win_private_assemblies=False,
|
| 19 |
+
cipher=block_cipher,
|
| 20 |
+
noarchive=False,
|
| 21 |
+
)
|
| 22 |
+
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
| 23 |
+
|
| 24 |
+
exe = EXE(
|
| 25 |
+
pyz,
|
| 26 |
+
a.scripts,
|
| 27 |
+
a.binaries,
|
| 28 |
+
a.zipfiles,
|
| 29 |
+
a.datas,
|
| 30 |
+
[],
|
| 31 |
+
name='main',
|
| 32 |
+
debug=False,
|
| 33 |
+
bootloader_ignore_signals=False,
|
| 34 |
+
strip=False,
|
| 35 |
+
upx=True,
|
| 36 |
+
upx_exclude=[],
|
| 37 |
+
runtime_tmpdir=None,
|
| 38 |
+
console=True,
|
| 39 |
+
disable_windowed_traceback=False,
|
| 40 |
+
argv_emulation=False,
|
| 41 |
+
target_arch=None,
|
| 42 |
+
codesign_identity=None,
|
| 43 |
+
entitlements_file=None,
|
| 44 |
+
)
|
node_client.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
|
| 2 |
# ================================================================
|
| 3 |
# node_client.py – عميل تسجيل العُقدة في نظام AmalOffload
|
| 4 |
# ---------------------------------------------------------------
|
|
@@ -21,15 +21,20 @@ from typing import Iterable, Tuple, List
|
|
| 21 |
DEFAULT_PORTS = {
|
| 22 |
7520, 7384, 9021, 6998, 5810, 9274,
|
| 23 |
8645, 7329, 7734, 8456, 6173, 7860,
|
|
|
|
| 24 |
}
|
| 25 |
|
| 26 |
# ⬇️ خوادم السجل الاحتياطية بالترتيب المفضَّل
|
| 27 |
DEFAULT_REGISTRY_SERVERS = [
|
|
|
|
|
|
|
| 28 |
"https://cv4790811.regru.cloud",
|
| 29 |
"https://amaloffload.onrender.com",
|
| 30 |
"https://osamabyc86-offload.hf.space",
|
| 31 |
"http://10.229.36.125",
|
| 32 |
"http://10.229.228.178",
|
|
|
|
|
|
|
| 33 |
]
|
| 34 |
|
| 35 |
logging.basicConfig(
|
|
@@ -56,8 +61,10 @@ class NodeClient:
|
|
| 56 |
self.node_id = node_id or os.getenv("NODE_ID", socket.gethostname())
|
| 57 |
|
| 58 |
# مبدئيًّا اختَر منفذًا (أولوية للمتغيّر البيئي إن وُجد)
|
| 59 |
-
self.port: int = int(os.getenv("CPU_PORT", random.choice(list(self.PORTs))))
|
| 60 |
self.current_server_index: int | None = None
|
|
|
|
|
|
|
| 61 |
|
| 62 |
# -------------------------------------------------------------------------
|
| 63 |
@staticmethod
|
|
@@ -66,10 +73,15 @@ class NodeClient:
|
|
| 66 |
try:
|
| 67 |
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
| 68 |
# لا يهم أن ينجح الاتصال الفعلي، الهدف كشف IP واجهة الخروج
|
| 69 |
-
s.connect(("
|
| 70 |
return s.getsockname()[0]
|
| 71 |
except Exception:
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
def _register_once(self, server: str, port: int) -> List[str]:
|
| 75 |
"""مُحاولة واحدة للتسجيل؛ تُعيد peers أو ترفع استثناءً."""
|
|
@@ -77,32 +89,106 @@ class NodeClient:
|
|
| 77 |
"node_id": self.node_id,
|
| 78 |
"ip": self.get_local_ip(),
|
| 79 |
"port": port,
|
|
|
|
|
|
|
| 80 |
}
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
# -------------------------------------------------------------------------
|
| 86 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
"""
|
| 88 |
يدور على جميع المنافذ والخوادم حتى ينجح التسجيل.
|
| 89 |
• عند النجاح يُرجع: (عنوان الخادم، قائمة الأقران)
|
| 90 |
• لا يرفع استثناءات؛ إمّا ينجح أو يستمر في المحاولة إلى ما لا نهاية.
|
| 91 |
"""
|
| 92 |
logging.info("🔄 بدء محاولات التسجيل للعقدة '%s'...", self.node_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
while True:
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
try:
|
| 97 |
peers = self._register_once(server, port)
|
| 98 |
# سجّل النجاح واحفظ المعلومات
|
| 99 |
self.port = port
|
| 100 |
self.current_server_index = idx
|
| 101 |
-
logging.info("✅
|
|
|
|
| 102 |
return server, peers
|
| 103 |
except Exception as e:
|
| 104 |
logging.debug("❌ %s:%s -> %s", server, port, e)
|
| 105 |
-
|
|
|
|
|
|
|
| 106 |
|
| 107 |
# -------------------------------------------------------------------------
|
| 108 |
def run_background(self) -> None:
|
|
@@ -110,8 +196,25 @@ class NodeClient:
|
|
| 110 |
إطلاق التسجيل في خيط منفصل؛ مفيد إذا كنت تريد
|
| 111 |
إبقاء Main Thread للمهام الأخرى.
|
| 112 |
"""
|
| 113 |
-
import threading
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
|
| 116 |
|
| 117 |
# -----------------------------------------------------------------------------
|
|
@@ -120,6 +223,24 @@ if __name__ == "__main__":
|
|
| 120 |
للتجربة المباشرة:
|
| 121 |
$ python node_client.py
|
| 122 |
"""
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
# ================================================================
|
| 3 |
# node_client.py – عميل تسجيل العُقدة في نظام AmalOffload
|
| 4 |
# ---------------------------------------------------------------
|
|
|
|
| 21 |
DEFAULT_PORTS = {
|
| 22 |
7520, 7384, 9021, 6998, 5810, 9274,
|
| 23 |
8645, 7329, 7734, 8456, 6173, 7860,
|
| 24 |
+
8080, 8000, 5000, 3000, 8888, 9999
|
| 25 |
}
|
| 26 |
|
| 27 |
# ⬇️ خوادم السجل الاحتياطية بالترتيب المفضَّل
|
| 28 |
DEFAULT_REGISTRY_SERVERS = [
|
| 29 |
+
"http://localhost:8888", # خادم محلي أولاً
|
| 30 |
+
"http://127.0.0.1:8888", # خادم محلي بديل
|
| 31 |
"https://cv4790811.regru.cloud",
|
| 32 |
"https://amaloffload.onrender.com",
|
| 33 |
"https://osamabyc86-offload.hf.space",
|
| 34 |
"http://10.229.36.125",
|
| 35 |
"http://10.229.228.178",
|
| 36 |
+
"http://192.168.1.1:8888", # راوتر محلي
|
| 37 |
+
"http://192.168.0.1:8888", # راوتر محلي بديل
|
| 38 |
]
|
| 39 |
|
| 40 |
logging.basicConfig(
|
|
|
|
| 61 |
self.node_id = node_id or os.getenv("NODE_ID", socket.gethostname())
|
| 62 |
|
| 63 |
# مبدئيًّا اختَر منفذًا (أولوية للمتغيّر البيئي إن وُجد)
|
| 64 |
+
self.port: int = int(os.getenv("CPU_PORT", random.choice(list(self.PORTs)))) # 🔧 تصحيح: PORTs بدلاً من PORTS
|
| 65 |
self.current_server_index: int | None = None
|
| 66 |
+
self.session = requests.Session()
|
| 67 |
+
self.session.timeout = 10
|
| 68 |
|
| 69 |
# -------------------------------------------------------------------------
|
| 70 |
@staticmethod
|
|
|
|
| 73 |
try:
|
| 74 |
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
| 75 |
# لا يهم أن ينجح الاتصال الفعلي، الهدف كشف IP واجهة الخروج
|
| 76 |
+
s.connect(("8.8.8.8", 53))
|
| 77 |
return s.getsockname()[0]
|
| 78 |
except Exception:
|
| 79 |
+
try:
|
| 80 |
+
# محاولة بديلة
|
| 81 |
+
hostname = socket.gethostname()
|
| 82 |
+
return socket.gethostbyname(hostname)
|
| 83 |
+
except Exception:
|
| 84 |
+
return "127.0.0.1"
|
| 85 |
|
| 86 |
def _register_once(self, server: str, port: int) -> List[str]:
|
| 87 |
"""مُحاولة واحدة للتسجيل؛ تُعيد peers أو ترفع استثناءً."""
|
|
|
|
| 89 |
"node_id": self.node_id,
|
| 90 |
"ip": self.get_local_ip(),
|
| 91 |
"port": port,
|
| 92 |
+
"hostname": socket.gethostname(),
|
| 93 |
+
"timestamp": time.time()
|
| 94 |
}
|
| 95 |
+
|
| 96 |
+
# جرب مسارات مختلفة للتسجيل
|
| 97 |
+
endpoints = ["/register", "/api/register", "/nodes/register", "/peer/register"]
|
| 98 |
+
|
| 99 |
+
for endpoint in endpoints:
|
| 100 |
+
try:
|
| 101 |
+
url = f"{server.rstrip('/')}{endpoint}"
|
| 102 |
+
logging.info(f"🔗 محاولة التسجيل في: {url}")
|
| 103 |
+
resp = self.session.post(url, json=payload, timeout=8)
|
| 104 |
+
|
| 105 |
+
if resp.status_code == 200:
|
| 106 |
+
data = resp.json()
|
| 107 |
+
logging.info(f"✅ تسجيل ناجح في {server}")
|
| 108 |
+
return data.get("peers", []) if isinstance(data, dict) else data
|
| 109 |
+
elif resp.status_code == 404:
|
| 110 |
+
continue # جرب endpoint التالي
|
| 111 |
+
else:
|
| 112 |
+
resp.raise_for_status()
|
| 113 |
+
|
| 114 |
+
except requests.exceptions.Timeout:
|
| 115 |
+
logging.warning(f"⏰ انتهت المهلة مع {server}{endpoint}")
|
| 116 |
+
continue
|
| 117 |
+
except requests.exceptions.ConnectionError:
|
| 118 |
+
logging.warning(f"🔌 تعذر الاتصال بـ {server}{endpoint}")
|
| 119 |
+
continue
|
| 120 |
+
except Exception as e:
|
| 121 |
+
logging.warning(f"❌ خطأ مع {server}{endpoint}: {e}")
|
| 122 |
+
continue
|
| 123 |
+
|
| 124 |
+
# إذا وصلنا هنا، فكل المحاولات فشلت
|
| 125 |
+
raise Exception(f"فشل التسجيل في {server} بعد تجربة جميع المسارات")
|
| 126 |
|
| 127 |
# -------------------------------------------------------------------------
|
| 128 |
+
def discover_local_servers(self) -> List[str]:
|
| 129 |
+
"""اكتشاف خوادم محلية على الشبكة."""
|
| 130 |
+
local_servers = []
|
| 131 |
+
base_ip = ".".join(self.get_local_ip().split(".")[:-1])
|
| 132 |
+
|
| 133 |
+
# فحص نطاق IPs المحلي
|
| 134 |
+
for i in range(1, 50): # فحص أول 50 عنوان فقط للسرعة
|
| 135 |
+
if i == int(self.get_local_ip().split(".")[-1]):
|
| 136 |
+
continue # تخطي الذات
|
| 137 |
+
|
| 138 |
+
ip = f"{base_ip}.{i}"
|
| 139 |
+
for port in [8888, 8000, 5000, 3000]:
|
| 140 |
+
server_url = f"http://{ip}:{port}"
|
| 141 |
+
if self.check_server_availability(server_url):
|
| 142 |
+
local_servers.append(server_url)
|
| 143 |
+
logging.info(f"🔍 تم اكتشاف خادم محلي: {server_url}")
|
| 144 |
+
|
| 145 |
+
return local_servers
|
| 146 |
+
|
| 147 |
+
def check_server_availability(self, server_url: str) -> bool:
|
| 148 |
+
"""فحص توفر الخادم."""
|
| 149 |
+
try:
|
| 150 |
+
resp = self.session.get(f"{server_url}/status", timeout=2)
|
| 151 |
+
return resp.status_code == 200
|
| 152 |
+
except:
|
| 153 |
+
return False
|
| 154 |
+
|
| 155 |
+
def connect_until_success(self, retry_delay: int = 10) -> Tuple[str, List[str]]:
|
| 156 |
"""
|
| 157 |
يدور على جميع المنافذ والخوادم حتى ينجح التسجيل.
|
| 158 |
• عند النجاح يُرجع: (عنوان الخادم، قائمة الأقران)
|
| 159 |
• لا يرفع استثناءات؛ إمّا ينجح أو يستمر في المحاولة إلى ما لا نهاية.
|
| 160 |
"""
|
| 161 |
logging.info("🔄 بدء محاولات التسجيل للعقدة '%s'...", self.node_id)
|
| 162 |
+
logging.info("🌐 عنوان IP المحلي: %s", self.get_local_ip())
|
| 163 |
+
logging.info("📋 عدد الخوادم المتاحة: %d", len(self.registry_servers))
|
| 164 |
+
|
| 165 |
+
attempt = 0
|
| 166 |
while True:
|
| 167 |
+
attempt += 1
|
| 168 |
+
logging.info("🔄 محاولة التسجيل رقم %d", attempt)
|
| 169 |
+
|
| 170 |
+
# اكتشاف خوادم محلية أولاً
|
| 171 |
+
if attempt % 3 == 1: # كل 3 محاولات، اكتشف خوادم محلية
|
| 172 |
+
local_servers = self.discover_local_servers()
|
| 173 |
+
all_servers = local_servers + self.registry_servers
|
| 174 |
+
else:
|
| 175 |
+
all_servers = self.registry_servers
|
| 176 |
+
|
| 177 |
+
for port in self.PORTs: # 🔧 تصحيح: PORTs بدلاً من PORTS
|
| 178 |
+
for idx, server in enumerate(all_servers):
|
| 179 |
try:
|
| 180 |
peers = self._register_once(server, port)
|
| 181 |
# سجّل النجاح واحفظ المعلومات
|
| 182 |
self.port = port
|
| 183 |
self.current_server_index = idx
|
| 184 |
+
logging.info("✅ تسجيل ناجح: %s على المنفذ %s", server, port)
|
| 185 |
+
logging.info("👥 عدد الأقران المكتشفين: %d", len(peers))
|
| 186 |
return server, peers
|
| 187 |
except Exception as e:
|
| 188 |
logging.debug("❌ %s:%s -> %s", server, port, e)
|
| 189 |
+
|
| 190 |
+
logging.warning("❌ فشلت جميع محاولات التسجيل، إعادة المحاولة بعد %d ثواني", retry_delay)
|
| 191 |
+
time.sleep(retry_delay)
|
| 192 |
|
| 193 |
# -------------------------------------------------------------------------
|
| 194 |
def run_background(self) -> None:
|
|
|
|
| 196 |
إطلاق التسجيل في خيط منفصل؛ مفيد إذا كنت تريد
|
| 197 |
إبقاء Main Thread للمهام الأخرى.
|
| 198 |
"""
|
| 199 |
+
import threading
|
| 200 |
+
def background_connect():
|
| 201 |
+
try:
|
| 202 |
+
server, peers = self.connect_until_success()
|
| 203 |
+
logging.info("🎯 التسجيل الخلفي ناجح مع %s", server)
|
| 204 |
+
except Exception as e:
|
| 205 |
+
logging.error("💥 خطأ في التسجيل الخلفي: %s", e)
|
| 206 |
+
|
| 207 |
+
threading.Thread(target=background_connect, daemon=True).start()
|
| 208 |
+
|
| 209 |
+
def get_current_info(self) -> dict:
|
| 210 |
+
"""الحصول على معلومات العقدة الحالية."""
|
| 211 |
+
return {
|
| 212 |
+
"node_id": self.node_id,
|
| 213 |
+
"ip": self.get_local_ip(),
|
| 214 |
+
"port": self.port,
|
| 215 |
+
"hostname": socket.gethostname(),
|
| 216 |
+
"current_server": self.registry_servers[self.current_server_index] if self.current_server_index is not None else None
|
| 217 |
+
}
|
| 218 |
|
| 219 |
|
| 220 |
# -----------------------------------------------------------------------------
|
|
|
|
| 223 |
للتجربة المباشرة:
|
| 224 |
$ python node_client.py
|
| 225 |
"""
|
| 226 |
+
try:
|
| 227 |
+
client = NodeClient()
|
| 228 |
+
print("🔍 جاري اكتشاف الخوادم المحلية...")
|
| 229 |
+
local_servers = client.discover_local_servers()
|
| 230 |
+
if local_servers:
|
| 231 |
+
print(f"✅ تم اكتشاف {len(local_servers)} خادم محلي")
|
| 232 |
+
|
| 233 |
+
print("🚀 بدء عملية التسجيل...")
|
| 234 |
+
server, peer_list = client.connect_until_success()
|
| 235 |
+
print(f"✅ تسجيل ناجح مع الخادم: {server}")
|
| 236 |
+
print(f"👥 عدد الأقران: {len(peer_list)}")
|
| 237 |
+
if peer_list:
|
| 238 |
+
print("📋 قائمة الأقران:")
|
| 239 |
+
for i, peer in enumerate(peer_list[:10]): # عرض أول 10 أقران فقط
|
| 240 |
+
print(f" {i+1}. {peer}")
|
| 241 |
+
if len(peer_list) > 10:
|
| 242 |
+
print(f" ... و{len(peer_list) - 10} آخرين")
|
| 243 |
+
except KeyboardInterrupt:
|
| 244 |
+
print("\n🛑 تم إيقاف العميل بواسطة المستخدم")
|
| 245 |
+
except Exception as e:
|
| 246 |
+
print(f"💥 خطأ غير متوقع: {e}")
|
offload_port.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
1000
|
peer_discovery.py
CHANGED
|
@@ -21,20 +21,24 @@ def get_sequential_port():
|
|
| 21 |
if current_port > 9999:
|
| 22 |
current_port = 1000
|
| 23 |
return port
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
PORT =
|
| 26 |
SERVICE = "_tasknode._tcp.local."
|
| 27 |
PEERS = set()
|
| 28 |
PEERS_INFO = {}
|
| 29 |
|
| 30 |
CENTRAL_REGISTRY_SERVERS = [
|
| 31 |
-
"
|
| 32 |
-
"https://amaloffload.onrender.com",
|
| 33 |
"https://osamabyc86-offload.hf.space",
|
| 34 |
"https://osamabyc19866-omsd.hf.space",
|
| 35 |
"https://52.13.128.108",
|
| 36 |
-
"https://176.28.
|
| 37 |
-
"https://44.229.227.142"
|
|
|
|
| 38 |
]
|
| 39 |
|
| 40 |
def get_local_ip():
|
|
@@ -51,47 +55,192 @@ def register_peer(ip, port):
|
|
| 51 |
peer_url = f"http://{ip}:{port}/run"
|
| 52 |
if peer_url not in PEERS:
|
| 53 |
PEERS.add(peer_url)
|
| 54 |
-
logger.info(f"تم تسجيل قرين جديد: {peer_url}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
def discover_lan_peers():
|
| 57 |
-
|
|
|
|
| 58 |
def add_service(self, zc, type_, name):
|
| 59 |
info = zc.get_service_info(type_, name)
|
| 60 |
-
if info:
|
| 61 |
ip = socket.inet_ntoa(info.addresses[0])
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
def main():
|
| 69 |
-
logger.info("🚀 بدء نظام اكتشاف
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
try:
|
| 87 |
while True:
|
| 88 |
-
logger.info(f"
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
except KeyboardInterrupt:
|
| 91 |
logger.info("🛑 إيقاف النظام...")
|
| 92 |
finally:
|
| 93 |
-
zeroconf
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
| 95 |
|
| 96 |
if __name__ == "__main__":
|
| 97 |
main()
|
|
|
|
| 21 |
if current_port > 9999:
|
| 22 |
current_port = 1000
|
| 23 |
return port
|
| 24 |
+
PORT = current_port
|
| 25 |
+
with open("offload_port.txt", "w") as f:
|
| 26 |
+
f.write(str(current_port))
|
| 27 |
|
| 28 |
+
PORT = int(os.getenv("CPU_PORT", get_sequential_port()))
|
| 29 |
SERVICE = "_tasknode._tcp.local."
|
| 30 |
PEERS = set()
|
| 31 |
PEERS_INFO = {}
|
| 32 |
|
| 33 |
CENTRAL_REGISTRY_SERVERS = [
|
| 34 |
+
"http://cv5303201.regru.cloud",
|
| 35 |
+
"https://amaloffload.onrender.com",
|
| 36 |
"https://osamabyc86-offload.hf.space",
|
| 37 |
"https://osamabyc19866-omsd.hf.space",
|
| 38 |
"https://52.13.128.108",
|
| 39 |
+
"https://176.28.156.149",
|
| 40 |
+
"https://44.229.227.142",
|
| 41 |
+
"https://osamabyc86-amalcoreflow.hf.space"
|
| 42 |
]
|
| 43 |
|
| 44 |
def get_local_ip():
|
|
|
|
| 55 |
peer_url = f"http://{ip}:{port}/run"
|
| 56 |
if peer_url not in PEERS:
|
| 57 |
PEERS.add(peer_url)
|
| 58 |
+
logger.info(f"✅ تم تسجيل قرين جديد: {peer_url}")
|
| 59 |
+
|
| 60 |
+
def register_with_central_servers():
|
| 61 |
+
"""تسجيل هذه العقدة على الخوادم المركزية"""
|
| 62 |
+
local_ip = get_local_ip()
|
| 63 |
+
node_info = {
|
| 64 |
+
"node_id": socket.gethostname(),
|
| 65 |
+
"ip": local_ip,
|
| 66 |
+
"port": PORT,
|
| 67 |
+
"hostname": socket.gethostname(),
|
| 68 |
+
"timestamp": time.time(),
|
| 69 |
+
"capabilities": ["cpu", "gpu", "storage"]
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
successful_registrations = 0
|
| 73 |
+
|
| 74 |
+
for server in CENTRAL_REGISTRY_SERVERS:
|
| 75 |
+
for endpoint in ["/register", "/api/register", "/nodes/register", "/peer/register"]:
|
| 76 |
+
try:
|
| 77 |
+
url = f"{server.rstrip('/')}{endpoint}"
|
| 78 |
+
logger.info(f"🔗 محاولة التسجيل في: {url}")
|
| 79 |
+
|
| 80 |
+
response = requests.post(
|
| 81 |
+
url,
|
| 82 |
+
json=node_info,
|
| 83 |
+
timeout=10,
|
| 84 |
+
headers={"Content-Type": "application/json"}
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
if response.status_code == 200:
|
| 88 |
+
data = response.json()
|
| 89 |
+
successful_registrations += 1
|
| 90 |
+
logger.info(f"✅ تم التسجيل بنجاح في: {server}")
|
| 91 |
+
|
| 92 |
+
# إضافة الأقران من الاستجابة
|
| 93 |
+
if isinstance(data, dict) and "peers" in data:
|
| 94 |
+
for peer in data["peers"]:
|
| 95 |
+
if isinstance(peer, str) and peer.startswith("http"):
|
| 96 |
+
PEERS.add(peer)
|
| 97 |
+
logger.info(f"👥 تمت إضافة قرين من السجل: {peer}")
|
| 98 |
+
|
| 99 |
+
break # توقف عن تجربة endpoints أخرى لهذا الخادم
|
| 100 |
+
|
| 101 |
+
elif response.status_code == 404:
|
| 102 |
+
continue # جرب endpoint التالي
|
| 103 |
+
else:
|
| 104 |
+
logger.warning(f"⚠️ استجابة غير متوقعة من {server}: {response.status_code}")
|
| 105 |
+
|
| 106 |
+
except requests.exceptions.Timeout:
|
| 107 |
+
logger.warning(f"⏰ انتهت المهلة مع {server}")
|
| 108 |
+
continue
|
| 109 |
+
except requests.exceptions.ConnectionError:
|
| 110 |
+
logger.warning(f"🔌 تعذر الاتصال بـ {server}")
|
| 111 |
+
continue
|
| 112 |
+
except Exception as e:
|
| 113 |
+
logger.warning(f"❌ خطأ مع {server}: {str(e)}")
|
| 114 |
+
continue
|
| 115 |
+
|
| 116 |
+
logger.info(f"📊 إجمالي التسجيلات الناجحة: {successful_registrations}/{len(CENTRAL_REGISTRY_SERVERS)}")
|
| 117 |
+
return successful_registrations > 0
|
| 118 |
|
| 119 |
def discover_lan_peers():
|
| 120 |
+
"""اكتشاف الأقران على الشبكة المحلية باستخدام Zeroconf"""
|
| 121 |
+
class PeerListener:
|
| 122 |
def add_service(self, zc, type_, name):
|
| 123 |
info = zc.get_service_info(type_, name)
|
| 124 |
+
if info and info.addresses:
|
| 125 |
ip = socket.inet_ntoa(info.addresses[0])
|
| 126 |
+
port = info.port
|
| 127 |
+
register_peer(ip, port)
|
| 128 |
+
logger.info(f"🔍 تم اكتشاف قرين محلي: {ip}:{port}")
|
| 129 |
+
|
| 130 |
+
try:
|
| 131 |
+
zeroconf = Zeroconf()
|
| 132 |
+
ServiceBrowser(zeroconf, SERVICE, PeerListener())
|
| 133 |
+
return zeroconf
|
| 134 |
+
except Exception as e:
|
| 135 |
+
logger.error(f"❌ فشل في بدء اكتشاف LAN: {e}")
|
| 136 |
+
return None
|
| 137 |
|
| 138 |
+
def start_periodic_registration(interval=60):
|
| 139 |
+
"""بدء التسجيل الدوري مع الخوادم المركزية"""
|
| 140 |
+
def registration_loop():
|
| 141 |
+
while True:
|
| 142 |
+
try:
|
| 143 |
+
logger.info("🔄 بدء التسجيل الدوري مع الخوادم المركزية...")
|
| 144 |
+
register_with_central_servers()
|
| 145 |
+
logger.info(f"📈 عدد الأقران الحالي: {len(PEERS)}")
|
| 146 |
+
time.sleep(interval)
|
| 147 |
+
except Exception as e:
|
| 148 |
+
logger.error(f"💥 خطأ في التسجيل الدوري: {e}")
|
| 149 |
+
time.sleep(interval)
|
| 150 |
+
|
| 151 |
+
thread = threading.Thread(target=registration_loop, daemon=True)
|
| 152 |
+
thread.start()
|
| 153 |
+
return thread
|
| 154 |
+
|
| 155 |
+
def start_periodic_discovery(interval=30):
|
| 156 |
+
"""اكتشاف دوري للأقران المحليين"""
|
| 157 |
+
def discovery_loop():
|
| 158 |
+
while True:
|
| 159 |
+
try:
|
| 160 |
+
# فحص نطاق IPs المحلي لاكتشاف الأقران
|
| 161 |
+
local_ip = get_local_ip()
|
| 162 |
+
base_ip = ".".join(local_ip.split(".")[:-1])
|
| 163 |
+
|
| 164 |
+
for i in range(1, 50):
|
| 165 |
+
if i == int(local_ip.split(".")[-1]):
|
| 166 |
+
continue # تخطي الذات
|
| 167 |
+
|
| 168 |
+
ip = f"{base_ip}.{i}"
|
| 169 |
+
for port in [PORT, 8888, 8000, 5000, 3000]:
|
| 170 |
+
peer_url = f"http://{ip}:{port}"
|
| 171 |
+
if check_peer_availability(peer_url):
|
| 172 |
+
register_peer(ip, port)
|
| 173 |
+
|
| 174 |
+
time.sleep(interval)
|
| 175 |
+
except Exception as e:
|
| 176 |
+
logger.error(f"💥 خطأ في الاكتشاف الدوري: {e}")
|
| 177 |
+
time.sleep(interval)
|
| 178 |
+
|
| 179 |
+
thread = threading.Thread(target=discovery_loop, daemon=True)
|
| 180 |
+
thread.start()
|
| 181 |
+
return thread
|
| 182 |
+
|
| 183 |
+
def check_peer_availability(peer_url):
|
| 184 |
+
"""فحص توفر القرين"""
|
| 185 |
+
try:
|
| 186 |
+
response = requests.get(f"{peer_url}/status", timeout=3)
|
| 187 |
+
return response.status_code == 200
|
| 188 |
+
except:
|
| 189 |
+
return False
|
| 190 |
+
|
| 191 |
+
def get_peers():
|
| 192 |
+
"""الحصول على قائمة الأقران المتاحة"""
|
| 193 |
+
return list(PEERS)
|
| 194 |
|
| 195 |
def main():
|
| 196 |
+
logger.info("🚀 بدء نظام اكتشاف الأقران المتقدم...")
|
| 197 |
+
logger.info(f"🌐 العقدة: {socket.gethostname()} - {get_local_ip()}:{PORT}")
|
| 198 |
+
|
| 199 |
+
# تسجيل الخدمة المحلية باستخدام Zeroconf
|
| 200 |
+
try:
|
| 201 |
+
zeroconf = Zeroconf()
|
| 202 |
+
info = ServiceInfo(
|
| 203 |
+
type_=SERVICE,
|
| 204 |
+
name=f"{socket.gethostname()}.{SERVICE}",
|
| 205 |
+
addresses=[socket.inet_aton(get_local_ip())],
|
| 206 |
+
port=PORT,
|
| 207 |
+
properties={b'version': b'1.0', b'hostname': socket.gethostname().encode()},
|
| 208 |
+
server=f"{socket.gethostname()}.local."
|
| 209 |
+
)
|
| 210 |
+
zeroconf.register_service(info)
|
| 211 |
+
logger.info("✅ تم تسجيل الخدمة المحلية باستخدام Zeroconf")
|
| 212 |
+
except Exception as e:
|
| 213 |
+
logger.error(f"❌ فشل في تسجيل Zeroconf: {e}")
|
| 214 |
+
zeroconf = None
|
| 215 |
+
|
| 216 |
+
# بدء الاكتشاف المحلي
|
| 217 |
+
lan_zeroconf = discover_lan_peers()
|
| 218 |
+
|
| 219 |
+
# بدء التسجيل الدوري مع الخوادم المركزية
|
| 220 |
+
registration_thread = start_periodic_registration(interval=60)
|
| 221 |
+
|
| 222 |
+
# بدء الاكتشاف الدوري
|
| 223 |
+
discovery_thread = start_periodic_discovery(interval=45)
|
| 224 |
|
| 225 |
try:
|
| 226 |
while True:
|
| 227 |
+
logger.info(f"📊 إحصائيات - الأقران: {len(PEERS)}")
|
| 228 |
+
if PEERS:
|
| 229 |
+
logger.info("📋 قائمة الأقران المتاحة:")
|
| 230 |
+
for i, peer in enumerate(list(PEERS)[:5]): # عرض أول 5 فقط
|
| 231 |
+
logger.info(f" {i+1}. {peer}")
|
| 232 |
+
if len(PEERS) > 5:
|
| 233 |
+
logger.info(f" ... و{len(PEERS) - 5} آخرين")
|
| 234 |
+
|
| 235 |
+
time.sleep(30)
|
| 236 |
except KeyboardInterrupt:
|
| 237 |
logger.info("🛑 إيقاف النظام...")
|
| 238 |
finally:
|
| 239 |
+
if zeroconf:
|
| 240 |
+
zeroconf.unregister_service(info)
|
| 241 |
+
zeroconf.close()
|
| 242 |
+
if lan_zeroconf:
|
| 243 |
+
lan_zeroconf.close()
|
| 244 |
|
| 245 |
if __name__ == "__main__":
|
| 246 |
main()
|
processor_manager.py
CHANGED
|
@@ -11,7 +11,7 @@ class ResourceMonitor:
|
|
| 11 |
self.cpu_history = deque(maxlen=10)
|
| 12 |
self.mem_history = deque(maxlen=10)
|
| 13 |
# حد استقبال المهمات الآن 40% CPU بدل 30%
|
| 14 |
-
self.receive_cpu_threshold = 0.
|
| 15 |
|
| 16 |
def current_load(self):
|
| 17 |
cpu = psutil.cpu_percent(interval=0.5) / 100.0 # كنسبة (0.0 - 1.0)
|
|
@@ -47,7 +47,7 @@ def should_offload(task_complexity=0):
|
|
| 47 |
avg_cpu = status['average']['cpu']
|
| 48 |
avg_mem = status['average']['mem']
|
| 49 |
|
| 50 |
-
if avg_cpu > 0.
|
| 51 |
trigger_offload()
|
| 52 |
return True
|
| 53 |
|
|
|
|
| 11 |
self.cpu_history = deque(maxlen=10)
|
| 12 |
self.mem_history = deque(maxlen=10)
|
| 13 |
# حد استقبال المهمات الآن 40% CPU بدل 30%
|
| 14 |
+
self.receive_cpu_threshold = 0.30
|
| 15 |
|
| 16 |
def current_load(self):
|
| 17 |
cpu = psutil.cpu_percent(interval=0.5) / 100.0 # كنسبة (0.0 - 1.0)
|
|
|
|
| 47 |
avg_cpu = status['average']['cpu']
|
| 48 |
avg_mem = status['average']['mem']
|
| 49 |
|
| 50 |
+
if avg_cpu > 0.7 or avg_mem < 2048 or task_complexity > 75:
|
| 51 |
trigger_offload()
|
| 52 |
return True
|
| 53 |
|
quick_connection_test.py
CHANGED
|
@@ -1,49 +1,206 @@
|
|
| 1 |
-
|
| 2 |
#!/usr/bin/env python3
|
| 3 |
# اختبار سريع للتحقق من حالة النظام
|
| 4 |
|
| 5 |
import requests
|
| 6 |
import time
|
| 7 |
import json
|
| 8 |
-
|
|
|
|
| 9 |
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
try:
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
print("✅ الخادم المحلي يعمل")
|
| 19 |
-
else:
|
| 20 |
-
print("❌ مشكلة في الخادم المحلي")
|
| 21 |
-
return False
|
| 22 |
except:
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
print("⚙️ اختبار مهمة بسيطة...")
|
| 33 |
-
start_time = time.time()
|
| 34 |
try:
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
duration = time.time() - start_time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
print(f"✅ تمت المعالجة في {duration:.2f} ثانية")
|
| 38 |
-
print(f"📊 النتيجة:
|
|
|
|
| 39 |
return True
|
|
|
|
| 40 |
except Exception as e:
|
| 41 |
print(f"❌ فشل في المعالجة: {e}")
|
| 42 |
return False
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
if __name__ == "__main__":
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
| 48 |
else:
|
| 49 |
-
print("\n
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
# اختبار سريع للتحقق من حالة النظام
|
| 3 |
|
| 4 |
import requests
|
| 5 |
import time
|
| 6 |
import json
|
| 7 |
+
import socket
|
| 8 |
+
import os
|
| 9 |
|
| 10 |
+
# استيراد الملفات المحلية
|
| 11 |
+
try:
|
| 12 |
+
from peer_discovery import PORT, get_local_ip, get_peers
|
| 13 |
+
from load_balancer import send_task
|
| 14 |
+
from smart_tasks import prime_calculation
|
| 15 |
+
except ImportError:
|
| 16 |
+
print("⚠️ بعض الوحدات غير متوفرة، جرب تشغيل: python launcher.py --tray")
|
| 17 |
+
|
| 18 |
+
def get_available_port():
|
| 19 |
+
"""الحصول على منفذ متاح"""
|
| 20 |
try:
|
| 21 |
+
from peer_discovery import PORT
|
| 22 |
+
return PORT
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
except:
|
| 24 |
+
return 8888 # منفذ افتراضي
|
| 25 |
+
|
| 26 |
+
def test_local_server():
|
| 27 |
+
"""فحص الخادم المحلي"""
|
| 28 |
+
port = get_available_port()
|
| 29 |
+
endpoints = ["/health", "/status", "/", "/api/status"]
|
| 30 |
|
| 31 |
+
for endpoint in endpoints:
|
| 32 |
+
try:
|
| 33 |
+
url = f"http://localhost:{port}{endpoint}"
|
| 34 |
+
response = requests.get(url, timeout=5)
|
| 35 |
+
if response.status_code == 200:
|
| 36 |
+
print(f"✅ الخادم المحلي يعمل على: {url}")
|
| 37 |
+
try:
|
| 38 |
+
data = response.json()
|
| 39 |
+
print(f"📊 حالة النظام: {data}")
|
| 40 |
+
except:
|
| 41 |
+
print("📄 استجابة نصية")
|
| 42 |
+
return True
|
| 43 |
+
except requests.exceptions.ConnectionError:
|
| 44 |
+
continue
|
| 45 |
+
except requests.exceptions.Timeout:
|
| 46 |
+
print(f"⏰ انتهت المهلة مع: localhost:{port}{endpoint}")
|
| 47 |
+
continue
|
| 48 |
+
except Exception as e:
|
| 49 |
+
print(f"❌ خطأ مع localhost:{port}{endpoint}: {e}")
|
| 50 |
+
continue
|
| 51 |
|
| 52 |
+
print("❌ الخادم المحلي غير متاح")
|
| 53 |
+
return False
|
| 54 |
+
|
| 55 |
+
def test_peer_discovery():
|
| 56 |
+
"""اختبار اكتشاف الأقران"""
|
| 57 |
+
print("🔍 اختبار اكتشاف الأقران...")
|
| 58 |
+
try:
|
| 59 |
+
# محاولة استيراد واستخدام peer_discovery
|
| 60 |
+
from peer_discovery import PEERS, get_peers
|
| 61 |
+
|
| 62 |
+
local_ip = get_local_ip()
|
| 63 |
+
print(f"🌐 IP المحلي: {local_ip}")
|
| 64 |
+
print(f"🔌 المنفذ: {get_available_port()}")
|
| 65 |
+
|
| 66 |
+
peers = list(PEERS) if PEERS else []
|
| 67 |
+
if hasattr(get_peers, '__call__'):
|
| 68 |
+
additional_peers = get_peers()
|
| 69 |
+
if additional_peers:
|
| 70 |
+
peers.extend(additional_peers)
|
| 71 |
+
|
| 72 |
+
print(f"📱 تم اكتشاف {len(peers)} جهاز")
|
| 73 |
+
for i, peer in enumerate(peers[:5]): # عرض أول 5 أقران فقط
|
| 74 |
+
print(f" {i+1}. {peer}")
|
| 75 |
+
|
| 76 |
+
return len(peers) > 0
|
| 77 |
+
except Exception as e:
|
| 78 |
+
print(f"⚠️ تعذر اكتشاف الأقران: {e}")
|
| 79 |
+
return False
|
| 80 |
+
|
| 81 |
+
def test_basic_task():
|
| 82 |
+
"""اختبار مهمة بسيطة"""
|
| 83 |
print("⚙️ اختبار مهمة بسيطة...")
|
|
|
|
| 84 |
try:
|
| 85 |
+
start_time = time.time()
|
| 86 |
+
|
| 87 |
+
# استخدام prime_calculation مباشرة
|
| 88 |
+
result = prime_calculation(1000) # عدد أصغر للاختبار السريع
|
| 89 |
+
|
| 90 |
duration = time.time() - start_time
|
| 91 |
+
|
| 92 |
+
if "error" in result:
|
| 93 |
+
print(f"❌ فشل في المعالجة: {result['error']}")
|
| 94 |
+
return False
|
| 95 |
+
|
| 96 |
print(f"✅ تمت المعالجة في {duration:.2f} ثانية")
|
| 97 |
+
print(f"📊 النتيجة: {result.get('count', 0)} أعداد أولية")
|
| 98 |
+
print(f"🔢 أول 5 أعداد: {result.get('primes', [])[:5]}")
|
| 99 |
return True
|
| 100 |
+
|
| 101 |
except Exception as e:
|
| 102 |
print(f"❌ فشل في المعالجة: {e}")
|
| 103 |
return False
|
| 104 |
|
| 105 |
+
def test_load_balancer():
|
| 106 |
+
"""اختبار موزع الحمل"""
|
| 107 |
+
print("⚖️ اختبار موزع الحمل...")
|
| 108 |
+
try:
|
| 109 |
+
from load_balancer import send_task
|
| 110 |
+
|
| 111 |
+
result = send_task("prime_calculation", 500)
|
| 112 |
+
|
| 113 |
+
if "error" in result:
|
| 114 |
+
print(f"⚠️ مو��ع الحمل: {result['error']}")
|
| 115 |
+
return False
|
| 116 |
+
else:
|
| 117 |
+
print(f"✅ موزع الحمل يعمل - {result.get('count', 0)} أعداد أولية")
|
| 118 |
+
return True
|
| 119 |
+
except Exception as e:
|
| 120 |
+
print(f"⚠️ تعذر اختبار موزع الحمل: {e}")
|
| 121 |
+
return False
|
| 122 |
+
|
| 123 |
+
def test_network_connectivity():
|
| 124 |
+
"""اختبار اتصال الشبكة"""
|
| 125 |
+
print("🌐 اختبار اتصال الشبكة...")
|
| 126 |
+
|
| 127 |
+
# فحص الاتصال بالإنترنت
|
| 128 |
+
try:
|
| 129 |
+
response = requests.get("http://www.google.com", timeout=5)
|
| 130 |
+
print("✅ الاتصال بالإنترنت متاح")
|
| 131 |
+
internet = True
|
| 132 |
+
except:
|
| 133 |
+
print("❌ لا يوجد اتصال بالإنترنت")
|
| 134 |
+
internet = False
|
| 135 |
+
|
| 136 |
+
# فحص الشبكة المحلية
|
| 137 |
+
try:
|
| 138 |
+
local_ip = socket.gethostbyname(socket.gethostname())
|
| 139 |
+
print(f"🏠 الشبكة المحلية: {local_ip}")
|
| 140 |
+
local_network = True
|
| 141 |
+
except:
|
| 142 |
+
print("❌ مشكلة في الشبكة المحلية")
|
| 143 |
+
local_network = False
|
| 144 |
+
|
| 145 |
+
return internet or local_network
|
| 146 |
+
|
| 147 |
+
def test_connection():
|
| 148 |
+
"""اختبار سريع للاتصال"""
|
| 149 |
+
print("=" * 50)
|
| 150 |
+
print("🚀 اختبار اتصال سريع لنظام AmalOffload")
|
| 151 |
+
print("=" * 50)
|
| 152 |
+
|
| 153 |
+
tests = [
|
| 154 |
+
("🌐 اتصال الشبكة", test_network_connectivity),
|
| 155 |
+
("🖥️ الخادم المحلي", test_local_server),
|
| 156 |
+
("🔍 اكتشاف الأقران", test_peer_discovery),
|
| 157 |
+
("⚙️ المهمة الأساسية", test_basic_task),
|
| 158 |
+
("⚖️ موزع الحمل", test_load_balancer),
|
| 159 |
+
]
|
| 160 |
+
|
| 161 |
+
results = []
|
| 162 |
+
|
| 163 |
+
for test_name, test_func in tests:
|
| 164 |
+
print(f"\n{test_name}...")
|
| 165 |
+
try:
|
| 166 |
+
result = test_func()
|
| 167 |
+
results.append(result)
|
| 168 |
+
time.sleep(1) # فاصل بين الاختبارات
|
| 169 |
+
except Exception as e:
|
| 170 |
+
print(f"💥 خطأ غير متوقع: {e}")
|
| 171 |
+
results.append(False)
|
| 172 |
+
|
| 173 |
+
print("\n" + "=" * 50)
|
| 174 |
+
print("📊 نتائج الاختبار:")
|
| 175 |
+
print("=" * 50)
|
| 176 |
+
|
| 177 |
+
success_count = sum(results)
|
| 178 |
+
total_tests = len(results)
|
| 179 |
+
|
| 180 |
+
for i, (test_name, _) in enumerate(tests):
|
| 181 |
+
status = "✅" if results[i] else "❌"
|
| 182 |
+
print(f"{status} {test_name}")
|
| 183 |
+
|
| 184 |
+
print(f"\n🎯 النتيجة النهائية: {success_count}/{total_tests}")
|
| 185 |
+
|
| 186 |
+
if success_count == total_tests:
|
| 187 |
+
print("🎉 النظام يعمل بشكل ممتاز!")
|
| 188 |
+
elif success_count >= 3:
|
| 189 |
+
print("👍 النظام يعمل بشكل جيد")
|
| 190 |
+
else:
|
| 191 |
+
print("⚠️ هناك مشاكل تحتاج إصلاح")
|
| 192 |
+
|
| 193 |
+
return success_count >= 3 # يعتبر ناجحاً إذا نجح 3/5 اختبارات على الأقل
|
| 194 |
+
|
| 195 |
if __name__ == "__main__":
|
| 196 |
+
success = test_connection()
|
| 197 |
+
|
| 198 |
+
if success:
|
| 199 |
+
print("\n💡 يمكنك الآن تشغيل:")
|
| 200 |
+
print(" python launcher.py --tray (لتشغيل النظام)")
|
| 201 |
+
print(" python test_distributed_system.py (لاختبار متقدم)")
|
| 202 |
else:
|
| 203 |
+
print("\n🔧 اقتراحات الإصلاح:")
|
| 204 |
+
print(" 1. تأكد من تشغيل: python background_service.py start")
|
| 205 |
+
print(" 2. تحقق من إعدادات الجدار الناري")
|
| 206 |
+
print(" 3. جرب: python peer_discovery.py (لاكتشاف الأقران)")
|
ram_manager.py
CHANGED
|
@@ -1,35 +1,39 @@
|
|
|
|
|
| 1 |
"""
|
| 2 |
-
ram_manager.py – Distributed RAM Offload Agent
|
| 3 |
-
|
| 4 |
|
| 5 |
❖ الغرض
|
| 6 |
-
|
| 7 |
-
يوفِّر هذا الملف إضافة مستقلة إلى مشروع **AmalOffload** من أجل مشاركة الذاكرة (RAM)
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
*
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
| 25 |
export RAM_THRESHOLD_MB=2048
|
| 26 |
export RAM_CHUNK_MB=64
|
|
|
|
|
|
|
| 27 |
python ram_manager.py
|
| 28 |
-
```
|
| 29 |
-
|
| 30 |
-
❖ التعليمات البرمجيّة
|
| 31 |
-
----------------------
|
| 32 |
"""
|
|
|
|
|
|
|
|
|
|
| 33 |
import os
|
| 34 |
import psutil
|
| 35 |
import time
|
|
@@ -37,125 +41,325 @@ import threading
|
|
| 37 |
import socket
|
| 38 |
import base64
|
| 39 |
import uuid
|
| 40 |
-
|
|
|
|
|
|
|
| 41 |
|
| 42 |
try:
|
| 43 |
from flask import Flask, request, jsonify
|
| 44 |
except ImportError as exc:
|
| 45 |
raise RuntimeError("Flask غير مُثبّت. نفِّذ: pip install flask") from exc
|
| 46 |
|
| 47 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
try:
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
| 52 |
|
| 53 |
-
# الإعدادات
|
| 54 |
-
RAM_LIMIT_MB
|
| 55 |
-
CHUNK_MB
|
| 56 |
-
CHECK_INTERVAL
|
| 57 |
-
RAM_PORT
|
| 58 |
|
| 59 |
app = Flask(__name__)
|
| 60 |
|
| 61 |
-
#
|
| 62 |
-
remote_chunks: Dict[str, bytes] = {}
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
def get_free_ram_mb() -> int:
|
| 65 |
-
"""الذاكرة الحرّة
|
| 66 |
-
return psutil.virtual_memory().available // (1024 * 1024)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
-
# ─────────────────── واجهة HTTP
|
| 69 |
-
|
|
|
|
| 70 |
def ram_status():
|
| 71 |
-
"""إرجاع كميّة الذاكرة الحرّة
|
| 72 |
-
return jsonify(
|
| 73 |
|
| 74 |
-
@app.
|
| 75 |
def ram_store():
|
| 76 |
-
"""تلقّي كتلة بيانات
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
blob = remote_chunks.get(cid)
|
| 86 |
if blob is None:
|
| 87 |
-
return jsonify({"error": "
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
# ─────────────────── وظائف داخليّة ───────────────────
|
| 91 |
|
| 92 |
def start_api():
|
| 93 |
-
"""تشغيل خادم Flask في خيط
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
def discover_peers() -> List[str]:
|
| 100 |
-
"""الحصول على قائمة IPs
|
| 101 |
-
|
| 102 |
|
| 103 |
-
|
|
|
|
| 104 |
try:
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
-
|
|
|
|
|
|
|
| 122 |
|
|
|
|
|
|
|
| 123 |
|
| 124 |
def offload_chunk(blob: bytes, peer_ip: str) -> bool:
|
| 125 |
-
"""إرسال كتلة بيانات إلى
|
| 126 |
-
import requests
|
| 127 |
try:
|
| 128 |
-
|
|
|
|
|
|
|
| 129 |
f"http://{peer_ip}:{RAM_PORT}/ram_store",
|
| 130 |
-
json={"id":
|
| 131 |
-
timeout=
|
| 132 |
)
|
| 133 |
-
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
return False
|
| 136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
def monitor_loop():
|
| 139 |
"""مراقبة الذاكرة واستدعاء offload عند الحاجة."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
while True:
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
| 152 |
else:
|
| 153 |
-
|
| 154 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
def main():
|
| 158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
monitor_loop()
|
| 160 |
|
| 161 |
if __name__ == "__main__":
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
"""
|
| 3 |
+
ram_manager.py – Distributed RAM Offload Agent (fixed)
|
| 4 |
+
=====================================================
|
| 5 |
|
| 6 |
❖ الغرض
|
| 7 |
+
--------
|
| 8 |
+
يوفِّر هذا الملف إضافة مستقلة إلى مشروع **AmalOffload** من أجل مشاركة الذاكرة (RAM)
|
| 9 |
+
بين جميع العُقد التي تشغِّل المشروع. عندما ينخفض مقدار الذاكرة الحرة على إحدى
|
| 10 |
+
العُقد إلى أقل من حدّ معيّن، تُنقل كتل بيانات إلى عُقد أخرى تملك ذاكرة حرّة.
|
| 11 |
+
|
| 12 |
+
❖ المزايا
|
| 13 |
+
---------
|
| 14 |
+
* مراقبة استهلاك الذاكرة محليًّا.
|
| 15 |
+
* إعلان واكتشاف الأقران (Peers) إن توفّرت وحدات المشروع.
|
| 16 |
+
* واجهة HTTP بسيطة عبر Flask:
|
| 17 |
+
- GET /ram_status → حالة الذاكرة.
|
| 18 |
+
- POST /ram_store → استلام كتلة بيانات (Base64) وتخزينها في الذاكرة.
|
| 19 |
+
- GET /ram_fetch/<id> → إرجاع كتلة بيانات محفوظة.
|
| 20 |
+
- GET /ram_info → معلومات عامة عن التخزين.
|
| 21 |
+
- GET /health → فحص الصحة.
|
| 22 |
+
|
| 23 |
+
طريقة التشغيل (أمثلة):
|
| 24 |
+
---------------------
|
| 25 |
+
python ram_manager.py --ram-limit 2048 --chunk 64 --interval 5 --port 8765
|
| 26 |
+
|
| 27 |
+
أو باستخدام متغيّرات البيئة:
|
| 28 |
export RAM_THRESHOLD_MB=2048
|
| 29 |
export RAM_CHUNK_MB=64
|
| 30 |
+
export RAM_CHECK_INTERVAL=10
|
| 31 |
+
export RAM_PORT=8765
|
| 32 |
python ram_manager.py
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
"""
|
| 34 |
+
|
| 35 |
+
from __future__ import annotations
|
| 36 |
+
|
| 37 |
import os
|
| 38 |
import psutil
|
| 39 |
import time
|
|
|
|
| 41 |
import socket
|
| 42 |
import base64
|
| 43 |
import uuid
|
| 44 |
+
import argparse
|
| 45 |
+
import logging
|
| 46 |
+
from typing import Dict, List, Optional
|
| 47 |
|
| 48 |
try:
|
| 49 |
from flask import Flask, request, jsonify
|
| 50 |
except ImportError as exc:
|
| 51 |
raise RuntimeError("Flask غير مُثبّت. نفِّذ: pip install flask") from exc
|
| 52 |
|
| 53 |
+
# إعداد السجلات
|
| 54 |
+
logging.basicConfig(
|
| 55 |
+
level=logging.INFO,
|
| 56 |
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
| 57 |
+
datefmt='%H:%M:%S'
|
| 58 |
+
)
|
| 59 |
+
logger = logging.getLogger(__name__)
|
| 60 |
+
|
| 61 |
+
# استيراد نظام الاكتشاف من المشروع (اختياري)
|
| 62 |
+
DISCOVERY_AVAILABLE = False
|
| 63 |
+
try:
|
| 64 |
+
from peer_discovery import PEERS, get_local_ip, get_peers # type: ignore
|
| 65 |
+
DISCOVERY_AVAILABLE = True
|
| 66 |
+
logger.info("✅ تم تحميل نظام الاكتشاف الموزع (peer_discovery)")
|
| 67 |
+
except Exception:
|
| 68 |
+
logger.warning("⚠️ نظام peer_discovery غير متوفر، سيتم استخدام بدائل بسيطة")
|
| 69 |
+
|
| 70 |
+
# استيراد peer_registry (Zeroconf) إن وجد
|
| 71 |
+
PEER_REGISTRY_AVAILABLE = False
|
| 72 |
try:
|
| 73 |
+
from peer_registry import discover_peers as discover_peers_zeroconf # type: ignore
|
| 74 |
+
PEER_REGISTRY_AVAILABLE = True
|
| 75 |
+
logger.info("✅ تم تحميل نظام تسجيل الأقران (peer_registry)")
|
| 76 |
+
except Exception:
|
| 77 |
+
logger.warning("⚠️ نظام peer_registry غير متوفر")
|
| 78 |
|
| 79 |
+
# الإعدادات الافتراضية (يمكن تعديلها عبر متغيّرات البيئة أو وسيطات سطر الأوامر)
|
| 80 |
+
RAM_LIMIT_MB = int(os.getenv("RAM_THRESHOLD_MB", "2048")) # حدّ الذاكرة الحرة قبل التفريغ
|
| 81 |
+
CHUNK_MB = int(os.getenv("RAM_CHUNK_MB", "64")) # حجم الكتلة المُرسلة
|
| 82 |
+
CHECK_INTERVAL = int(os.getenv("RAM_CHECK_INTERVAL", "10")) # فترة الفحص بالثواني
|
| 83 |
+
RAM_PORT = int(os.getenv("RAM_PORT", "8765")) # منفذ واجهة خدمة الذاكرة
|
| 84 |
|
| 85 |
app = Flask(__name__)
|
| 86 |
|
| 87 |
+
# مخازن الذاكرة
|
| 88 |
+
remote_chunks: Dict[str, bytes] = {} # كتل وصلت من أقران أخرى
|
| 89 |
+
local_chunks: Dict[str, bytes] = {} # تخزين محلي إن أردت استخدامه لاحقًا
|
| 90 |
+
|
| 91 |
+
# ─────────────────── وظائف مساعدة للذاكرة ───────────────────
|
| 92 |
|
| 93 |
def get_free_ram_mb() -> int:
|
| 94 |
+
"""إرجاع الذاكرة الحرّة بالميغابايت."""
|
| 95 |
+
return int(psutil.virtual_memory().available // (1024 * 1024))
|
| 96 |
+
|
| 97 |
+
def get_ram_usage_percent() -> float:
|
| 98 |
+
"""إرجاع نسبة استخدام الذاكرة."""
|
| 99 |
+
return float(psutil.virtual_memory().percent)
|
| 100 |
+
|
| 101 |
+
def get_system_info() -> Dict[str, int | float]:
|
| 102 |
+
"""الحصول على معلومات النظام الحالية الخاصة بالذاكرة."""
|
| 103 |
+
memory = psutil.virtual_memory()
|
| 104 |
+
return {
|
| 105 |
+
"total_mb": int(memory.total // (1024 * 1024)),
|
| 106 |
+
"available_mb": int(memory.available // (1024 * 1024)),
|
| 107 |
+
"used_mb": int(memory.used // (1024 * 1024)),
|
| 108 |
+
"usage_percent": float(memory.percent),
|
| 109 |
+
"threshold_mb": int(RAM_LIMIT_MB),
|
| 110 |
+
}
|
| 111 |
|
| 112 |
+
# ─────────────────── واجهة HTTP ───────────────────
|
| 113 |
+
|
| 114 |
+
@app.get("/ram_status")
|
| 115 |
def ram_status():
|
| 116 |
+
"""إرجاع كميّة الذاكرة الحرّة ومعلومات تفصيلية."""
|
| 117 |
+
return jsonify(get_system_info())
|
| 118 |
|
| 119 |
+
@app.post("/ram_store")
|
| 120 |
def ram_store():
|
| 121 |
+
"""تلقّي كتلة بيانات وتخزينها في الذاكرة (remote_chunks)."""
|
| 122 |
+
try:
|
| 123 |
+
payload = request.get_json(force=True, silent=False)
|
| 124 |
+
if not payload or not isinstance(payload, dict):
|
| 125 |
+
return jsonify({"error": "بيانات غير صالحة"}), 400
|
| 126 |
+
|
| 127 |
+
cid: Optional[str] = payload.get("id")
|
| 128 |
+
blob_b64: Optional[str] = payload.get("data")
|
| 129 |
+
|
| 130 |
+
if not cid or not blob_b64:
|
| 131 |
+
return jsonify({"error": "المعرّف أو البيانات مفقودة"}), 400
|
| 132 |
+
|
| 133 |
+
# فحص المساحة المتاحة قبل التخزين (شرط بسيط احترازي)
|
| 134 |
+
if get_free_ram_mb() < max(64, RAM_LIMIT_MB // 2):
|
| 135 |
+
return jsonify({"error": "مساحة ذاكرة غير كافية"}), 507
|
| 136 |
+
|
| 137 |
+
remote_chunks[cid] = base64.b64decode(blob_b64.encode())
|
| 138 |
+
logger.info("✅ تم تخزين كتلة بيانات: %s (%d بايت)", cid, len(remote_chunks[cid]))
|
| 139 |
+
return jsonify({"status": "stored", "id": cid, "size_bytes": len(remote_chunks[cid])})
|
| 140 |
+
except Exception as e:
|
| 141 |
+
logger.exception("❌ خطأ في التخزين")
|
| 142 |
+
return jsonify({"error": str(e)}), 500
|
| 143 |
+
|
| 144 |
+
@app.get("/ram_fetch/<cid>")
|
| 145 |
+
def ram_fetch(cid: str):
|
| 146 |
+
"""جلب كتلة بيانات مخزنة عبر معرّفها."""
|
| 147 |
blob = remote_chunks.get(cid)
|
| 148 |
if blob is None:
|
| 149 |
+
return jsonify({"error": "الكتلة غير موجودة"}), 404
|
| 150 |
+
|
| 151 |
+
return jsonify({
|
| 152 |
+
"id": cid,
|
| 153 |
+
"data": base64.b64encode(blob).decode(),
|
| 154 |
+
"size_bytes": len(blob),
|
| 155 |
+
})
|
| 156 |
+
|
| 157 |
+
@app.get("/ram_info")
|
| 158 |
+
def ram_info():
|
| 159 |
+
"""معلومات عامة عن الكتل المخزنة محليًا وبعيدًا."""
|
| 160 |
+
return jsonify({
|
| 161 |
+
"local_chunks": len(local_chunks),
|
| 162 |
+
"remote_chunks": len(remote_chunks),
|
| 163 |
+
"local_size_mb": sum(len(b) for b in local_chunks.values()) // (1024 * 1024),
|
| 164 |
+
"remote_size_mb": sum(len(b) for b in remote_chunks.values()) // (1024 * 1024),
|
| 165 |
+
})
|
| 166 |
+
|
| 167 |
+
@app.get("/health")
|
| 168 |
+
def health():
|
| 169 |
+
"""فحص صحة الخدمة."""
|
| 170 |
+
return jsonify({
|
| 171 |
+
"status": "healthy",
|
| 172 |
+
"service": "ram_manager",
|
| 173 |
+
"port": RAM_PORT,
|
| 174 |
+
"free_ram_mb": get_free_ram_mb(),
|
| 175 |
+
})
|
| 176 |
|
| 177 |
# ─────────────────── وظائف داخليّة ───────────────────
|
| 178 |
|
| 179 |
def start_api():
|
| 180 |
+
"""تشغيل خادم Flask في خيط منفصل باستخدام Werkzeug server."""
|
| 181 |
+
try:
|
| 182 |
+
from werkzeug.serving import make_server
|
| 183 |
+
host = "0.0.0.0"
|
| 184 |
+
server = make_server(host, RAM_PORT, app)
|
| 185 |
+
logger.info("🚀 بدء خادم الذاكرة الموزعة على %s:%d", host, RAM_PORT)
|
| 186 |
+
server.serve_forever()
|
| 187 |
+
except Exception as e:
|
| 188 |
+
logger.error("❌ فشل في بدء الخادم: %s", e)
|
| 189 |
|
| 190 |
+
def check_peer_availability(ip: str) -> bool:
|
| 191 |
+
"""فحص توفر قرين عبر endpoint /health."""
|
| 192 |
+
try:
|
| 193 |
+
import requests # محليًا لتفادي كونه تبعية صلبة إن لم تُستخدم
|
| 194 |
+
r = requests.get(f"http://{ip}:{RAM_PORT}/health", timeout=3)
|
| 195 |
+
return r.status_code == 200
|
| 196 |
+
except Exception:
|
| 197 |
+
return False
|
| 198 |
+
|
| 199 |
+
def _discover_peers_basic_scan(limit: int = 20) -> List[str]:
|
| 200 |
+
"""اكتشاف بسيط بمسح الشبكة إن لم تتوفر آليات المشروع."""
|
| 201 |
+
try:
|
| 202 |
+
if DISCOVERY_AVAILABLE:
|
| 203 |
+
local_ip = get_local_ip()
|
| 204 |
+
else:
|
| 205 |
+
local_ip = socket.gethostbyname(socket.gethostname())
|
| 206 |
+
base_ip = ".".join(local_ip.split(".")[:-1])
|
| 207 |
+
local_last = int(local_ip.split(".")[-1])
|
| 208 |
+
except Exception:
|
| 209 |
+
return []
|
| 210 |
+
|
| 211 |
+
peers_ips: List[str] = []
|
| 212 |
+
for i in range(1, limit + 1):
|
| 213 |
+
if i == local_last:
|
| 214 |
+
continue
|
| 215 |
+
ip = f"{base_ip}.{i}"
|
| 216 |
+
if check_peer_availability(ip):
|
| 217 |
+
peers_ips.append(ip)
|
| 218 |
+
logger.info("🔍 اكتشف مسح شبكة: %s", ip)
|
| 219 |
+
return peers_ips
|
| 220 |
|
| 221 |
def discover_peers() -> List[str]:
|
| 222 |
+
"""الحصول على قائمة IPs للأقران المتاحين، بدون تكرار."""
|
| 223 |
+
peers_ips: List[str] = []
|
| 224 |
|
| 225 |
+
# 1) Zeroconf عبر peer_registry إن توفر
|
| 226 |
+
if PEER_REGISTRY_AVAILABLE:
|
| 227 |
try:
|
| 228 |
+
peers_data = discover_peers_zeroconf(timeout=2) # type: ignore[arg-type]
|
| 229 |
+
for peer in peers_data:
|
| 230 |
+
if isinstance(peer, dict) and 'ip' in peer:
|
| 231 |
+
ip = str(peer['ip'])
|
| 232 |
+
if ip and ip not in peers_ips and check_peer_availability(ip):
|
| 233 |
+
peers_ips.append(ip)
|
| 234 |
+
logger.info("🔍 اكتشف zeroconf: %s", ip)
|
| 235 |
+
except Exception as e:
|
| 236 |
+
logger.warning("⚠️ خطأ في zeroconf: %s", e)
|
| 237 |
+
|
| 238 |
+
# 2) نظام الاكتشاف الداخلي للمشروع إن توفر
|
| 239 |
+
if DISCOVERY_AVAILABLE:
|
| 240 |
+
try:
|
| 241 |
+
plist = get_peers() if callable(get_peers) else [] # type: ignore[misc]
|
| 242 |
+
for p in plist:
|
| 243 |
+
if isinstance(p, str) and p.startswith("http://"):
|
| 244 |
+
# استخراج IP من URL
|
| 245 |
+
try:
|
| 246 |
+
ip = p.split("//", 1)[1].split(":", 1)[0]
|
| 247 |
+
except Exception:
|
| 248 |
+
continue
|
| 249 |
+
if ip and ip not in peers_ips and check_peer_availability(ip):
|
| 250 |
+
peers_ips.append(ip)
|
| 251 |
+
logger.info("🔍 اكتشف peer_discovery: %s", ip)
|
| 252 |
+
except Exception as e:
|
| 253 |
+
logger.warning("⚠️ خطأ في peer_discovery: %s", e)
|
| 254 |
|
| 255 |
+
# 3) بديل: مسح بسيط للشبكة
|
| 256 |
+
if not peers_ips:
|
| 257 |
+
peers_ips = _discover_peers_basic_scan(limit=20)
|
| 258 |
|
| 259 |
+
logger.info("📊 إجمالي الأقران المكتشفين: %d", len(peers_ips))
|
| 260 |
+
return peers_ips
|
| 261 |
|
| 262 |
def offload_chunk(blob: bytes, peer_ip: str) -> bool:
|
| 263 |
+
"""إرسال كتلة بيانات إلى قرين محدّد عبر /ram_store."""
|
|
|
|
| 264 |
try:
|
| 265 |
+
import requests
|
| 266 |
+
chunk_id = str(uuid.uuid4())
|
| 267 |
+
r = requests.post(
|
| 268 |
f"http://{peer_ip}:{RAM_PORT}/ram_store",
|
| 269 |
+
json={"id": chunk_id, "data": base64.b64encode(blob).decode()},
|
| 270 |
+
timeout=10
|
| 271 |
)
|
| 272 |
+
if r.status_code == 200:
|
| 273 |
+
logger.info("✅ تم إرسال كتلة %dMB إلى %s", len(blob) // (1024 * 1024), peer_ip)
|
| 274 |
+
return True
|
| 275 |
+
logger.warning("⚠️ رفض التخزين من %s: %s", peer_ip, r.status_code)
|
| 276 |
+
return False
|
| 277 |
+
except Exception as e:
|
| 278 |
+
logger.warning("🔌 فشل الإرسال إلى %s: %s", peer_ip, e)
|
| 279 |
return False
|
| 280 |
|
| 281 |
+
def create_sample_data(size_mb: int) -> bytes:
|
| 282 |
+
"""إنشاء بيانات عشوائية للاختبار بعدد ميغابايتات محدّد."""
|
| 283 |
+
return os.urandom(size_mb * 1024 * 1024)
|
| 284 |
+
|
| 285 |
+
def register_with_peer_registry():
|
| 286 |
+
"""تسجيل هذه العقدة في نظام Zeroconf (اختياري)."""
|
| 287 |
+
if not PEER_REGISTRY_AVAILABLE:
|
| 288 |
+
return
|
| 289 |
+
try:
|
| 290 |
+
from peer_registry import register_service # type: ignore
|
| 291 |
+
local_ip = get_local_ip() if DISCOVERY_AVAILABLE else socket.gethostbyname(socket.gethostname())
|
| 292 |
+
register_service(local_ip, RAM_PORT, load=0.0)
|
| 293 |
+
logger.info("✅ تم تسجيل العقدة في Zeroconf: %s:%d", local_ip, RAM_PORT)
|
| 294 |
+
except Exception as e:
|
| 295 |
+
logger.warning("⚠️ فشل التسجيل في Zeroconf: %s", e)
|
| 296 |
|
| 297 |
def monitor_loop():
|
| 298 |
"""مراقبة الذاكرة واستدعاء offload عند الحاجة."""
|
| 299 |
+
logger.info("🔍 بدء مراقبة الذاكرة...")
|
| 300 |
+
# تسجيل العقدة إن أمكن
|
| 301 |
+
register_with_peer_registry()
|
| 302 |
+
|
| 303 |
while True:
|
| 304 |
+
try:
|
| 305 |
+
free_mb = get_free_ram_mb()
|
| 306 |
+
usage_percent = get_ram_usage_percent()
|
| 307 |
+
|
| 308 |
+
# تسجيل الحالة كل ~30 ثانية (تقريبية بدون مؤقّت إضافي)
|
| 309 |
+
if int(time.time()) % 30 == 0:
|
| 310 |
+
logger.info("📊 حالة الذاكرة: %dMB حرّة (%.1f%% مستخدمة)", free_mb, usage_percent)
|
| 311 |
+
|
| 312 |
+
# إن كانت الذاكرة الحرة أقل من الحدّ، حاول التفريغ
|
| 313 |
+
if free_mb < RAM_LIMIT_MB:
|
| 314 |
+
logger.warning("🚨 الذاكرة منخفضة: %dMB (الحد: %dMB)", free_mb, RAM_LIMIT_MB)
|
| 315 |
+
peers = discover_peers()
|
| 316 |
+
if not peers:
|
| 317 |
+
logger.info("👥 لا يوجد أقران متاحون حاليًا.")
|
| 318 |
else:
|
| 319 |
+
# ⚠️ في التطبيق الفعلي، ينبغي تفريغ بيانات حقيقية من التطبيق
|
| 320 |
+
blob = create_sample_data(CHUNK_MB)
|
| 321 |
+
for ip in peers:
|
| 322 |
+
if offload_chunk(blob, ip):
|
| 323 |
+
logger.info("📤 تم تفريغ %dMB إلى %s", CHUNK_MB, ip)
|
| 324 |
+
break
|
| 325 |
+
else:
|
| 326 |
+
logger.warning("❌ جميع الأقران رفضوا التخزين")
|
| 327 |
|
| 328 |
+
time.sleep(CHECK_INTERVAL)
|
| 329 |
+
except Exception as e:
|
| 330 |
+
logger.error("💥 خطأ في مراقبة الذاكرة: %s", e)
|
| 331 |
+
time.sleep(CHECK_INTERVAL)
|
| 332 |
|
| 333 |
def main():
|
| 334 |
+
"""الدالة الرئيسية لتشغيل الخدمة."""
|
| 335 |
+
global RAM_LIMIT_MB, CHUNK_MB, CHECK_INTERVAL, RAM_PORT
|
| 336 |
+
|
| 337 |
+
parser = argparse.ArgumentParser(description="مدير الذاكرة الموزعة")
|
| 338 |
+
parser.add_argument("--ram-limit", type=int, default=RAM_LIMIT_MB,
|
| 339 |
+
help="حد الذاكرة الحرّة بالميجابايت")
|
| 340 |
+
parser.add_argument("--chunk", type=int, default=CHUNK_MB,
|
| 341 |
+
help="حجم الكتلة بالميجابايت")
|
| 342 |
+
parser.add_argument("--interval", type=int, default=CHECK_INTERVAL,
|
| 343 |
+
help="فترة المراقبة بالثواني")
|
| 344 |
+
parser.add_argument("--port", type=int, default=RAM_PORT,
|
| 345 |
+
help="منفذ الخدمة")
|
| 346 |
+
args = parser.parse_args()
|
| 347 |
+
|
| 348 |
+
# تحديث الإعدادات من الوسائط
|
| 349 |
+
RAM_LIMIT_MB = int(args.ram_limit)
|
| 350 |
+
CHUNK_MB = int(args.chunk)
|
| 351 |
+
CHECK_INTERVAL = int(args.interval)
|
| 352 |
+
RAM_PORT = int(args.port)
|
| 353 |
+
|
| 354 |
+
logger.info("🚀 بدء تشغيل مدير الذاكرة الموزعة")
|
| 355 |
+
logger.info("⚙️ الإعدادات: الحد %dMB, الكتلة %dMB, الفاصل %ds, المنفذ %d",
|
| 356 |
+
RAM_LIMIT_MB, CHUNK_MB, CHECK_INTERVAL, RAM_PORT)
|
| 357 |
+
|
| 358 |
+
# بدء الخادم في خيط منفصل
|
| 359 |
+
server_thread = threading.Thread(target=start_api, daemon=True)
|
| 360 |
+
server_thread.start()
|
| 361 |
+
|
| 362 |
+
# بدء حلقة المراقبة
|
| 363 |
monitor_loop()
|
| 364 |
|
| 365 |
if __name__ == "__main__":
|
requirements.txt
CHANGED
|
@@ -18,7 +18,7 @@ flask
|
|
| 18 |
GPUtil
|
| 19 |
requests
|
| 20 |
python-dotenv
|
| 21 |
-
|
| 22 |
flask_socketio
|
| 23 |
-
|
| 24 |
-
|
|
|
|
| 18 |
GPUtil
|
| 19 |
requests
|
| 20 |
python-dotenv
|
| 21 |
+
|
| 22 |
flask_socketio
|
| 23 |
+
|
| 24 |
+
|
rpc_server.py
CHANGED
|
@@ -6,11 +6,21 @@
|
|
| 6 |
# ============================================================
|
| 7 |
|
| 8 |
from flask import Flask, request, jsonify
|
| 9 |
-
import smart_tasks # «your_tasks» تمّ استيراده تحت هذا الاسم فى main.py
|
| 10 |
import logging, json
|
| 11 |
from security_layer import SecurityManager
|
| 12 |
from peer_discovery import PORT
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
SECURITY = SecurityManager("my_shared_secret_123")
|
| 15 |
|
| 16 |
logging.basicConfig(
|
|
@@ -19,7 +29,20 @@ logging.basicConfig(
|
|
| 19 |
format="%(asctime)s - %(levelname)s - %(message)s"
|
| 20 |
)
|
| 21 |
|
| 22 |
-
app = Flask(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
# ------------------------------------------------------------------
|
| 25 |
@app.route("/health")
|
|
@@ -30,45 +53,70 @@ def health():
|
|
| 30 |
@app.route("/run", methods=["POST"])
|
| 31 |
def run():
|
| 32 |
try:
|
|
|
|
|
|
|
| 33 |
# 1) حاول قراءة كـ JSON مباشر (وضع التطويـر)
|
| 34 |
if request.is_json:
|
| 35 |
data = request.get_json()
|
|
|
|
| 36 |
else:
|
| 37 |
# 2) وإلا اعتبره Payload مُشفَّر (وضع الإنتاج)
|
| 38 |
encrypted = request.get_data()
|
|
|
|
| 39 |
try:
|
| 40 |
decrypted = SECURITY.decrypt_data(encrypted)
|
| 41 |
-
data = json.loads(decrypted.decode())
|
|
|
|
| 42 |
except Exception as e:
|
| 43 |
logging.error(f"⚠️ فشل فك التشفير: {e}")
|
| 44 |
return jsonify(error="Decryption failed"), 400
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
# 3) التحقّق من التوقيع إن وُجد
|
| 47 |
if "_signature" in data:
|
|
|
|
| 48 |
if not SECURITY.verify_task(data):
|
| 49 |
logging.warning("❌ توقيع غير صالح")
|
| 50 |
return jsonify(error="Invalid signature"), 403
|
| 51 |
# أزل عناصر موقّعة إضافية
|
| 52 |
data = {k: v for k, v in data.items() if k not in ("_signature", "sender_id")}
|
|
|
|
| 53 |
|
|
|
|
| 54 |
func_name = data.get("func")
|
| 55 |
-
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
|
|
|
| 58 |
fn = getattr(smart_tasks, func_name, None)
|
| 59 |
-
if not fn:
|
| 60 |
logging.warning(f"❌ لم يتم العثور على الدالة: {func_name}")
|
| 61 |
-
return jsonify(error="Function not found"), 404
|
| 62 |
|
| 63 |
-
logging.info(f"⚙️ تنفيذ الدالة: {func_name}
|
|
|
|
|
|
|
| 64 |
result = fn(*args, **kwargs)
|
| 65 |
-
|
|
|
|
|
|
|
| 66 |
|
| 67 |
except Exception as e:
|
| 68 |
-
logging.error(f"🔥 خطأ أثناء تنفيذ المهمة: {str(e)}")
|
| 69 |
return jsonify(error=str(e)), 500
|
| 70 |
|
| 71 |
# ------------------------------------------------------------------
|
| 72 |
-
if
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
# ============================================================
|
| 7 |
|
| 8 |
from flask import Flask, request, jsonify
|
|
|
|
| 9 |
import logging, json
|
| 10 |
from security_layer import SecurityManager
|
| 11 |
from peer_discovery import PORT
|
| 12 |
|
| 13 |
+
# استيراد المهام - مع معالجة الخطأ
|
| 14 |
+
try:
|
| 15 |
+
import smart_tasks
|
| 16 |
+
except ImportError:
|
| 17 |
+
# إذا كان اسم الملف مختلف
|
| 18 |
+
try:
|
| 19 |
+
import your_tasks as smart_tasks
|
| 20 |
+
except ImportError:
|
| 21 |
+
logging.error("❌ لم يتم العثور على وحدة المهام")
|
| 22 |
+
raise
|
| 23 |
+
|
| 24 |
SECURITY = SecurityManager("my_shared_secret_123")
|
| 25 |
|
| 26 |
logging.basicConfig(
|
|
|
|
| 29 |
format="%(asctime)s - %(levelname)s - %(message)s"
|
| 30 |
)
|
| 31 |
|
| 32 |
+
app = Flask(__name__)
|
| 33 |
+
|
| 34 |
+
# ------------------------------------------------------------------
|
| 35 |
+
@app.route("/")
|
| 36 |
+
def index():
|
| 37 |
+
return jsonify({
|
| 38 |
+
"message": "AmalOffload RPC Server",
|
| 39 |
+
"version": "1.0",
|
| 40 |
+
"endpoints": {
|
| 41 |
+
"/health": "GET - فحص صحة الخادم",
|
| 42 |
+
"/run": "POST - تنفيذ المهام (يتطلب JSON)",
|
| 43 |
+
},
|
| 44 |
+
"status": "running"
|
| 45 |
+
})
|
| 46 |
|
| 47 |
# ------------------------------------------------------------------
|
| 48 |
@app.route("/health")
|
|
|
|
| 53 |
@app.route("/run", methods=["POST"])
|
| 54 |
def run():
|
| 55 |
try:
|
| 56 |
+
data = None
|
| 57 |
+
|
| 58 |
# 1) حاول قراءة كـ JSON مباشر (وضع التطويـر)
|
| 59 |
if request.is_json:
|
| 60 |
data = request.get_json()
|
| 61 |
+
logging.info("📥 تم استقبال بيانات JSON مباشرة (وضع التطوير)")
|
| 62 |
else:
|
| 63 |
# 2) وإلا اعتبره Payload مُشفَّر (وضع الإنتاج)
|
| 64 |
encrypted = request.get_data()
|
| 65 |
+
logging.info(f"🔐 تم استقبال بيانات مشفرة بحجم: {len(encrypted)} بايت")
|
| 66 |
try:
|
| 67 |
decrypted = SECURITY.decrypt_data(encrypted)
|
| 68 |
+
data = json.loads(decrypted.decode('utf-8'))
|
| 69 |
+
logging.info("✅ تم فك التشفير بنجاح")
|
| 70 |
except Exception as e:
|
| 71 |
logging.error(f"⚠️ فشل فك التشفير: {e}")
|
| 72 |
return jsonify(error="Decryption failed"), 400
|
| 73 |
|
| 74 |
+
# التحقق من وجود البيانات
|
| 75 |
+
if not data:
|
| 76 |
+
logging.warning("❌ لا توجد بيانات صالحة")
|
| 77 |
+
return jsonify(error="No valid data received"), 400
|
| 78 |
+
|
| 79 |
# 3) التحقّق من التوقيع إن وُجد
|
| 80 |
if "_signature" in data:
|
| 81 |
+
logging.info("🔏 التحقق من التوقيع...")
|
| 82 |
if not SECURITY.verify_task(data):
|
| 83 |
logging.warning("❌ توقيع غير صالح")
|
| 84 |
return jsonify(error="Invalid signature"), 403
|
| 85 |
# أزل عناصر موقّعة إضافية
|
| 86 |
data = {k: v for k, v in data.items() if k not in ("_signature", "sender_id")}
|
| 87 |
+
logging.info("✅ التوقيع صالح")
|
| 88 |
|
| 89 |
+
# التحقق من وجود اسم الدالة
|
| 90 |
func_name = data.get("func")
|
| 91 |
+
if not func_name:
|
| 92 |
+
logging.warning("❌ لم يتم تحديد اسم الدالة")
|
| 93 |
+
return jsonify(error="Function name 'func' is required"), 400
|
| 94 |
+
|
| 95 |
+
args = data.get("args", [])
|
| 96 |
+
kwargs = data.get("kwargs", {})
|
| 97 |
|
| 98 |
+
# البحث عن الدالة
|
| 99 |
fn = getattr(smart_tasks, func_name, None)
|
| 100 |
+
if not fn or not callable(fn):
|
| 101 |
logging.warning(f"❌ لم يتم العثور على الدالة: {func_name}")
|
| 102 |
+
return jsonify(error=f"Function '{func_name}' not found"), 404
|
| 103 |
|
| 104 |
+
logging.info(f"⚙️ تنفيذ الدالة: {func_name} مع args: {args}, kwargs: {kwargs}")
|
| 105 |
+
|
| 106 |
+
# تنفيذ الدالة
|
| 107 |
result = fn(*args, **kwargs)
|
| 108 |
+
|
| 109 |
+
logging.info(f"✅ تم تنفيذ الدالة {func_name} بنجاح")
|
| 110 |
+
return jsonify(result=result, status="success")
|
| 111 |
|
| 112 |
except Exception as e:
|
| 113 |
+
logging.error(f"🔥 خطأ أثناء تنفيذ المهمة: {str(e)}", exc_info=True)
|
| 114 |
return jsonify(error=str(e)), 500
|
| 115 |
|
| 116 |
# ------------------------------------------------------------------
|
| 117 |
+
if __name__ == "__main__":
|
| 118 |
+
logging.info(f"🚀 بدء تشغيل RPC Server على المنفذ {PORT}")
|
| 119 |
+
try:
|
| 120 |
+
app.run(host="0.0.0.0", port=PORT, debug=False)
|
| 121 |
+
except Exception as e:
|
| 122 |
+
logging.error(f"❌ فشل تشغيل الخادم: {e}")
|
run_task.py
CHANGED
|
@@ -1,8 +1,87 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
def run_task():
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
| 6 |
if request.is_json:
|
| 7 |
data = request.get_json()
|
| 8 |
task_id = data.get("task_id")
|
|
@@ -10,13 +89,103 @@ def run_task():
|
|
| 10 |
task_id = request.form.get("task_id")
|
| 11 |
|
| 12 |
if not task_id:
|
| 13 |
-
return jsonify(error
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
result =
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
run_task.py - خادم ويب بسيط لتشغيل المهام
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from flask import Flask, request, jsonify
|
| 7 |
+
import logging
|
| 8 |
+
import sys
|
| 9 |
+
import os
|
| 10 |
+
|
| 11 |
+
# إضافة المسار الحالي إلى Python path
|
| 12 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
| 13 |
+
|
| 14 |
+
# استيراد المهام
|
| 15 |
+
try:
|
| 16 |
+
from smart_tasks import matrix_multiply, prime_calculation, data_processing
|
| 17 |
+
TASKS_AVAILABLE = True
|
| 18 |
+
except ImportError as e:
|
| 19 |
+
logging.error(f"❌ لم يتم العثور على وحدة المهام: {e}")
|
| 20 |
+
TASKS_AVAILABLE = False
|
| 21 |
+
|
| 22 |
+
# إعداد السجلات
|
| 23 |
+
logging.basicConfig(
|
| 24 |
+
level=logging.INFO,
|
| 25 |
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
| 26 |
+
datefmt='%H:%M:%S'
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
# إنشاء تطبيق Flask
|
| 30 |
+
app = Flask(__name__)
|
| 31 |
+
|
| 32 |
+
@app.route("/")
|
| 33 |
+
def index():
|
| 34 |
+
"""الصفحة الرئيسية"""
|
| 35 |
+
return jsonify({
|
| 36 |
+
"message": "AmalOffload Task Runner",
|
| 37 |
+
"version": "1.0",
|
| 38 |
+
"status": "running",
|
| 39 |
+
"endpoints": {
|
| 40 |
+
"/": "GET - الصفحة الرئيسية",
|
| 41 |
+
"/health": "GET - فحص الصحة",
|
| 42 |
+
"/run_task": "POST - تشغيل المهام",
|
| 43 |
+
"/tasks": "GET - قائمة المهام المتاحة"
|
| 44 |
+
}
|
| 45 |
+
})
|
| 46 |
+
|
| 47 |
+
@app.route("/health")
|
| 48 |
+
def health():
|
| 49 |
+
"""فحص صحة الخادم"""
|
| 50 |
+
return jsonify({
|
| 51 |
+
"status": "healthy",
|
| 52 |
+
"tasks_available": TASKS_AVAILABLE
|
| 53 |
+
})
|
| 54 |
+
|
| 55 |
+
@app.route("/tasks")
|
| 56 |
+
def list_tasks():
|
| 57 |
+
"""عرض قائمة المهام المتاحة"""
|
| 58 |
+
tasks = {
|
| 59 |
+
"1": {
|
| 60 |
+
"name": "matrix_multiply",
|
| 61 |
+
"description": "ضرب المصفوفات بحجم 500x500",
|
| 62 |
+
"parameters": "matrix_size=500"
|
| 63 |
+
},
|
| 64 |
+
"2": {
|
| 65 |
+
"name": "prime_calculation",
|
| 66 |
+
"description": "حساب الأعداد الأولية حتى 100,000",
|
| 67 |
+
"parameters": "limit=100000"
|
| 68 |
+
},
|
| 69 |
+
"3": {
|
| 70 |
+
"name": "data_processing",
|
| 71 |
+
"description": "معالجة بيانات بحجم 10,000 عنصر",
|
| 72 |
+
"parameters": "size=10000"
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
return jsonify(tasks)
|
| 76 |
+
|
| 77 |
+
@app.route("/run_task", methods=["POST"])
|
| 78 |
def run_task():
|
| 79 |
+
"""تشغيل مهمة محددة"""
|
| 80 |
+
if not TASKS_AVAILABLE:
|
| 81 |
+
return jsonify({"error": "المهام غير متاحة"}), 500
|
| 82 |
+
|
| 83 |
+
# قبول البيانات سواء كانت JSON أو form-urlencoded
|
| 84 |
+
task_id = None
|
| 85 |
if request.is_json:
|
| 86 |
data = request.get_json()
|
| 87 |
task_id = data.get("task_id")
|
|
|
|
| 89 |
task_id = request.form.get("task_id")
|
| 90 |
|
| 91 |
if not task_id:
|
| 92 |
+
return jsonify({"error": "معرف المهمة مطلوب (task_id)"}), 400
|
| 93 |
+
|
| 94 |
+
logging.info(f"🎯 تشغيل المهمة: {task_id}")
|
| 95 |
+
|
| 96 |
+
try:
|
| 97 |
+
result = None
|
| 98 |
+
execution_time = None
|
| 99 |
+
import time
|
| 100 |
+
|
| 101 |
+
start_time = time.time()
|
| 102 |
+
|
| 103 |
+
if task_id == "1":
|
| 104 |
+
result = matrix_multiply(500)
|
| 105 |
+
elif task_id == "2":
|
| 106 |
+
result = prime_calculation(100000)
|
| 107 |
+
elif task_id == "3":
|
| 108 |
+
result = data_processing(10000)
|
| 109 |
+
else:
|
| 110 |
+
return jsonify({"error": f"المهمة {task_id} غير معروفة"}), 404
|
| 111 |
+
|
| 112 |
+
execution_time = time.time() - start_time
|
| 113 |
+
|
| 114 |
+
logging.info(f"✅ تم تنفيذ المهمة {task_id} في {execution_time:.2f} ثانية")
|
| 115 |
+
|
| 116 |
+
return jsonify({
|
| 117 |
+
"task_id": task_id,
|
| 118 |
+
"status": "success",
|
| 119 |
+
"execution_time": round(execution_time, 2),
|
| 120 |
+
"result": result
|
| 121 |
+
})
|
| 122 |
+
|
| 123 |
+
except Exception as e:
|
| 124 |
+
logging.error(f"❌ خطأ في تنفيذ المهمة {task_id}: {e}")
|
| 125 |
+
return jsonify({
|
| 126 |
+
"task_id": task_id,
|
| 127 |
+
"status": "error",
|
| 128 |
+
"error": str(e)
|
| 129 |
+
}), 500
|
| 130 |
+
|
| 131 |
+
@app.route("/run_task/<task_id>", methods=["GET"])
|
| 132 |
+
def run_task_get(task_id):
|
| 133 |
+
"""تشغيل مهمة عبر GET (للتجربة السريعة)"""
|
| 134 |
+
if not TASKS_AVAILABLE:
|
| 135 |
+
return jsonify({"error": "المهام غير متاحة"}), 500
|
| 136 |
+
|
| 137 |
+
logging.info(f"🎯 تشغيل المهمة عبر GET: {task_id}")
|
| 138 |
+
|
| 139 |
+
try:
|
| 140 |
+
result = None
|
| 141 |
+
execution_time = None
|
| 142 |
+
import time
|
| 143 |
+
|
| 144 |
+
start_time = time.time()
|
| 145 |
+
|
| 146 |
+
if task_id == "1":
|
| 147 |
+
result = matrix_multiply(500)
|
| 148 |
+
elif task_id == "2":
|
| 149 |
+
result = prime_calculation(100000)
|
| 150 |
+
elif task_id == "3":
|
| 151 |
+
result = data_processing(10000)
|
| 152 |
+
else:
|
| 153 |
+
return jsonify({"error": f"المهمة {task_id} غير معروفة"}), 404
|
| 154 |
+
|
| 155 |
+
execution_time = time.time() - start_time
|
| 156 |
+
|
| 157 |
+
logging.info(f"✅ تم تنفيذ المهمة {task_id} في {execution_time:.2f} ثانية")
|
| 158 |
+
|
| 159 |
+
return jsonify({
|
| 160 |
+
"task_id": task_id,
|
| 161 |
+
"status": "success",
|
| 162 |
+
"execution_time": round(execution_time, 2),
|
| 163 |
+
"result": result
|
| 164 |
+
})
|
| 165 |
+
|
| 166 |
+
except Exception as e:
|
| 167 |
+
logging.error(f"❌ خطأ في تنفيذ المهمة {task_id}: {e}")
|
| 168 |
+
return jsonify({
|
| 169 |
+
"task_id": task_id,
|
| 170 |
+
"status": "error",
|
| 171 |
+
"error": str(e)
|
| 172 |
+
}), 500
|
| 173 |
+
|
| 174 |
+
def main():
|
| 175 |
+
"""الدالة الرئيسية"""
|
| 176 |
+
port = 5613 # يمكن تغيير المنفذ إذا لزم الأمر
|
| 177 |
|
| 178 |
+
logging.info("🚀 بدء تشغيل خادم المهام...")
|
| 179 |
+
logging.info(f"📡 سيعمل الخادم على: http://localhost:{port}")
|
| 180 |
+
logging.info("📋 المهام المتاحة:")
|
| 181 |
+
logging.info(" 1 - ضرب المصفوفات (matrix_multiply)")
|
| 182 |
+
logging.info(" 2 - حساب الأعداد الأولية (prime_calculation)")
|
| 183 |
+
logging.info(" 3 - معالجة البيانات (data_processing)")
|
| 184 |
+
|
| 185 |
+
try:
|
| 186 |
+
app.run(host="0.0.0.0", port=port, debug=False)
|
| 187 |
+
except Exception as e:
|
| 188 |
+
logging.error(f"❌ فشل في تشغيل الخادم: {e}")
|
| 189 |
+
|
| 190 |
+
if __name__ == "__main__":
|
| 191 |
+
main()
|
simple_history.json
CHANGED
|
@@ -8,5 +8,15 @@
|
|
| 8 |
"timestamp": "2025-08-01T04:07:56.868610",
|
| 9 |
"user": "كيف حالك",
|
| 10 |
"assistant": "هذا سؤال جيد!"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
]
|
|
|
|
| 8 |
"timestamp": "2025-08-01T04:07:56.868610",
|
| 9 |
"user": "كيف حالك",
|
| 10 |
"assistant": "هذا سؤال جيد!"
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"timestamp": "2025-10-20T01:04:19.278752",
|
| 14 |
+
"user": "h",
|
| 15 |
+
"assistant": "حدثني أكثر عن ذلك."
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"timestamp": "2025-10-20T01:04:25.627297",
|
| 19 |
+
"user": "hi",
|
| 20 |
+
"assistant": "هذا سؤال جيد!"
|
| 21 |
}
|
| 22 |
]
|
system_tray.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
|
| 2 |
#!/usr/bin/env python3
|
| 3 |
"""
|
| 4 |
أيقونة شريط النظام للتحكم في الخدمة الخلفية
|
|
@@ -8,9 +7,11 @@ import sys
|
|
| 8 |
import threading
|
| 9 |
import requests
|
| 10 |
import webbrowser
|
|
|
|
|
|
|
|
|
|
| 11 |
from pathlib import Path
|
| 12 |
from peer_discovery import PORT
|
| 13 |
-
|
| 14 |
try:
|
| 15 |
import pystray
|
| 16 |
from pystray import MenuItem as item
|
|
@@ -24,150 +25,294 @@ class SystemTrayController:
|
|
| 24 |
def __init__(self):
|
| 25 |
self.base_url = "http://localhost:8888"
|
| 26 |
self.icon = None
|
|
|
|
|
|
|
| 27 |
|
| 28 |
-
def create_icon_image(self):
|
| 29 |
"""إنشاء صورة الأيقونة"""
|
| 30 |
# إنشاء صورة بسيطة 64x64
|
| 31 |
-
image = Image.new('RGB', (64, 64), color=
|
| 32 |
draw = ImageDraw.Draw(image)
|
| 33 |
|
| 34 |
# رسم دائرة بسيطة
|
| 35 |
draw.ellipse([16, 16, 48, 48], fill='white')
|
| 36 |
-
draw.ellipse([20, 20, 44, 44], fill=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
return image
|
| 39 |
|
| 40 |
-
def
|
| 41 |
-
"""
|
| 42 |
try:
|
| 43 |
response = requests.get(f"{self.base_url}/status", timeout=2)
|
| 44 |
-
|
|
|
|
|
|
|
| 45 |
except:
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
| 50 |
try:
|
| 51 |
-
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
except Exception as e:
|
| 54 |
-
print(f"فشل في بدء
|
|
|
|
| 55 |
|
| 56 |
-
def
|
| 57 |
-
"""إيقاف
|
| 58 |
try:
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
except Exception as e:
|
| 62 |
-
print(f"فشل في إيقاف
|
|
|
|
| 63 |
|
| 64 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
"""إظهار الواجهة التفاعلية"""
|
| 66 |
try:
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
webbrowser.open('http://localhost:5173')
|
|
|
|
| 70 |
except Exception as e:
|
| 71 |
-
print(f"فشل في
|
| 72 |
|
| 73 |
-
def
|
| 74 |
-
"""
|
| 75 |
try:
|
| 76 |
-
|
|
|
|
| 77 |
except Exception as e:
|
| 78 |
-
print(f"فشل في
|
| 79 |
|
| 80 |
-
def
|
| 81 |
-
"""فتح
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
-
def show_status(self, icon, item):
|
| 85 |
"""إظهار حالة النظام"""
|
| 86 |
-
status = self.
|
| 87 |
if status:
|
| 88 |
-
status_text = f"حالة النظام: {status
|
| 89 |
-
status_text += f"الخدمات النشطة: {len([s for s in status
|
| 90 |
-
status_text += f"وقت التشغيل: {int(status
|
| 91 |
print(status_text)
|
| 92 |
else:
|
| 93 |
-
print("❌
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
"""إنهاء التطبيق"""
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
"""تحديث قائمة الأيقونة"""
|
| 102 |
if self.icon:
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
menu = pystray.Menu(
|
| 107 |
-
item('
|
| 108 |
-
item('---'),
|
| 109 |
-
item('
|
| 110 |
-
item('
|
| 111 |
-
item('
|
| 112 |
-
item('
|
| 113 |
-
item('
|
| 114 |
-
item('
|
| 115 |
-
item('---'),
|
| 116 |
-
item('
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
)
|
| 118 |
|
| 119 |
self.icon.menu = menu
|
| 120 |
|
| 121 |
def create_menu(self):
|
| 122 |
"""إنشاء قائمة الأيقونة"""
|
|
|
|
|
|
|
|
|
|
| 123 |
return pystray.Menu(
|
| 124 |
-
item('
|
| 125 |
-
item('---'),
|
| 126 |
-
item('
|
| 127 |
-
item('
|
| 128 |
-
item('
|
| 129 |
-
item('
|
| 130 |
-
item('
|
| 131 |
-
item('
|
| 132 |
-
item('---'),
|
| 133 |
-
item('
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
)
|
| 135 |
|
| 136 |
def run(self):
|
| 137 |
"""تشغيل أيقونة شريط النظام"""
|
| 138 |
if not TRAY_AVAILABLE:
|
| 139 |
print("❌ مكتبة pystray غير متوفرة")
|
| 140 |
-
|
|
|
|
| 141 |
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
self.
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
def main():
|
|
|
|
| 169 |
controller = SystemTrayController()
|
| 170 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
if __name__ == "__main__":
|
| 173 |
main()
|
|
|
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
أيقونة شريط النظام للتحكم في الخدمة الخلفية
|
|
|
|
| 7 |
import threading
|
| 8 |
import requests
|
| 9 |
import webbrowser
|
| 10 |
+
import subprocess
|
| 11 |
+
import os
|
| 12 |
+
import time
|
| 13 |
from pathlib import Path
|
| 14 |
from peer_discovery import PORT
|
|
|
|
| 15 |
try:
|
| 16 |
import pystray
|
| 17 |
from pystray import MenuItem as item
|
|
|
|
| 25 |
def __init__(self):
|
| 26 |
self.base_url = "http://localhost:8888"
|
| 27 |
self.icon = None
|
| 28 |
+
self.background_process = None
|
| 29 |
+
self.services_running = False
|
| 30 |
|
| 31 |
+
def create_icon_image(self, color='blue'):
|
| 32 |
"""إنشاء صورة الأيقونة"""
|
| 33 |
# إنشاء صورة بسيطة 64x64
|
| 34 |
+
image = Image.new('RGB', (64, 64), color=color)
|
| 35 |
draw = ImageDraw.Draw(image)
|
| 36 |
|
| 37 |
# رسم دائرة بسيطة
|
| 38 |
draw.ellipse([16, 16, 48, 48], fill='white')
|
| 39 |
+
draw.ellipse([20, 20, 44, 44], fill=color)
|
| 40 |
+
|
| 41 |
+
# إضافة رمز صغير للإشارة للحالة
|
| 42 |
+
if self.services_running:
|
| 43 |
+
draw.rectangle([28, 28, 36, 36], fill='green') # نقطة خضراء عند التشغيل
|
| 44 |
+
else:
|
| 45 |
+
draw.rectangle([28, 28, 36, 36], fill='red') # نقطة حمراء عند التوقف
|
| 46 |
|
| 47 |
return image
|
| 48 |
|
| 49 |
+
def check_service_status(self):
|
| 50 |
+
"""فحص حالة الخدمة"""
|
| 51 |
try:
|
| 52 |
response = requests.get(f"{self.base_url}/status", timeout=2)
|
| 53 |
+
if response.status_code == 200:
|
| 54 |
+
self.services_running = True
|
| 55 |
+
return response.json()
|
| 56 |
except:
|
| 57 |
+
self.services_running = False
|
| 58 |
+
return None
|
| 59 |
+
|
| 60 |
+
def start_background_service(self):
|
| 61 |
+
"""بدء الخدمة الخلفية"""
|
| 62 |
try:
|
| 63 |
+
if self.background_process and self.background_process.poll() is None:
|
| 64 |
+
print("✅ الخدمة الخلفية تعمل بالفعل")
|
| 65 |
+
return True
|
| 66 |
+
|
| 67 |
+
print("🚀 بدء الخدمة الخلفية...")
|
| 68 |
+
self.background_process = subprocess.Popen(
|
| 69 |
+
[sys.executable, 'background_service.py', 'start'],
|
| 70 |
+
stdout=subprocess.PIPE,
|
| 71 |
+
stderr=subprocess.PIPE,
|
| 72 |
+
creationflags=subprocess.CREATE_NO_WINDOW
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
# انتظار حتى تبدأ الخدمة
|
| 76 |
+
for i in range(10):
|
| 77 |
+
time.sleep(1)
|
| 78 |
+
if self.check_service_status():
|
| 79 |
+
print("✅ تم بدء الخدمة الخلفية بنجاح")
|
| 80 |
+
self.services_running = True
|
| 81 |
+
return True
|
| 82 |
+
|
| 83 |
+
print("⚠️ بدأت الخدمة ولكنها لم تستجب بعد")
|
| 84 |
+
return False
|
| 85 |
+
|
| 86 |
except Exception as e:
|
| 87 |
+
print(f"❌ فشل في بدء الخدمة الخلفية: {e}")
|
| 88 |
+
return False
|
| 89 |
|
| 90 |
+
def stop_background_service(self):
|
| 91 |
+
"""إيقاف الخدمة الخلفية"""
|
| 92 |
try:
|
| 93 |
+
if self.background_process:
|
| 94 |
+
# محاولة إيقاف نظيف عبر HTTP
|
| 95 |
+
try:
|
| 96 |
+
requests.post(f"{self.base_url}/stop", timeout=5)
|
| 97 |
+
time.sleep(2)
|
| 98 |
+
except:
|
| 99 |
+
pass
|
| 100 |
+
|
| 101 |
+
# إجبار الإيقاف إذا لزم
|
| 102 |
+
if self.background_process.poll() is None:
|
| 103 |
+
self.background_process.terminate()
|
| 104 |
+
self.background_process.wait(timeout=5)
|
| 105 |
+
|
| 106 |
+
self.background_process = None
|
| 107 |
+
self.services_running = False
|
| 108 |
+
print("✅ تم إيقاف الخدمة الخلفية")
|
| 109 |
+
return True
|
| 110 |
+
else:
|
| 111 |
+
print("ℹ️ لا توجد خدمة خلفية قيد التشغيل")
|
| 112 |
+
return True
|
| 113 |
+
|
| 114 |
except Exception as e:
|
| 115 |
+
print(f"❌ فشل في إيقاف الخدمة الخلفية: {e}")
|
| 116 |
+
return False
|
| 117 |
|
| 118 |
+
def start_services(self, icon=None, item=None):
|
| 119 |
+
"""بدء تشغيل الخدمات"""
|
| 120 |
+
print("🔧 بدء جميع الخدمات...")
|
| 121 |
+
success = self.start_background_service()
|
| 122 |
+
if success:
|
| 123 |
+
self.update_icon_menu()
|
| 124 |
+
print("✅ تم بدء جميع الخدمات")
|
| 125 |
+
else:
|
| 126 |
+
print("❌ فشل في بدء الخدمات")
|
| 127 |
+
|
| 128 |
+
def stop_services(self, icon=None, item=None):
|
| 129 |
+
"""إيقاف الخدمات"""
|
| 130 |
+
print("🛑 إيقاف جميع الخدمات...")
|
| 131 |
+
success = self.stop_background_service()
|
| 132 |
+
if success:
|
| 133 |
+
self.update_icon_menu()
|
| 134 |
+
print("✅ تم إيقاف جميع الخدمات")
|
| 135 |
+
else:
|
| 136 |
+
print("❌ فشل في إيقاف الخدمات")
|
| 137 |
+
|
| 138 |
+
def show_ui(self, icon=None, item=None):
|
| 139 |
"""إظهار الواجهة التفاعلية"""
|
| 140 |
try:
|
| 141 |
+
# بدء الخدمة أولاً إذا لم تكن مشغلة
|
| 142 |
+
if not self.services_running:
|
| 143 |
+
self.start_background_service()
|
| 144 |
+
time.sleep(3)
|
| 145 |
+
|
| 146 |
+
# محاولة فتح الواجهة
|
| 147 |
webbrowser.open('http://localhost:5173')
|
| 148 |
+
print("🌐 فتح الواجهة في المتصفح...")
|
| 149 |
except Exception as e:
|
| 150 |
+
print(f"❌ فشل في فتح الواجهة: {e}")
|
| 151 |
|
| 152 |
+
def open_dashboard(self, icon=None, item=None):
|
| 153 |
+
"""فتح لوحة التحكم"""
|
| 154 |
try:
|
| 155 |
+
webbrowser.open('http://localhost:5173/dashboard')
|
| 156 |
+
print("📊 فتح لوحة التحكم...")
|
| 157 |
except Exception as e:
|
| 158 |
+
print(f"❌ فشل في فتح لوحة التحكم: {e}")
|
| 159 |
|
| 160 |
+
def open_web_interface(self, icon=None, item=None):
|
| 161 |
+
"""فتح واجهة الويب"""
|
| 162 |
+
try:
|
| 163 |
+
webbrowser.open('http://localhost:PORT')
|
| 164 |
+
print("🖥️ فتح واجهة المهام...")
|
| 165 |
+
except Exception as e:
|
| 166 |
+
print(f"❌ فشل في فتح واجهة المهام: {e}")
|
| 167 |
|
| 168 |
+
def show_status(self, icon=None, item=None):
|
| 169 |
"""إظهار حالة النظام"""
|
| 170 |
+
status = self.check_service_status()
|
| 171 |
if status:
|
| 172 |
+
status_text = f"✅ حالة النظام: {status.get('status', 'unknown')}\n"
|
| 173 |
+
status_text += f"📊 الخدمات النشطة: {len([s for s in status.get('services', {}).values() if s == 'running'])}\n"
|
| 174 |
+
status_text += f"⏱️ وقت التشغيل: {int(status.get('uptime', 0))} ثانية"
|
| 175 |
print(status_text)
|
| 176 |
else:
|
| 177 |
+
print("❌ الخدمات غير نشطة - استخدم 'بدء الخدمات' لتشغيلها")
|
| 178 |
+
|
| 179 |
+
def show_system_info(self, icon=None, item=None):
|
| 180 |
+
"""عرض معلومات النظام"""
|
| 181 |
+
try:
|
| 182 |
+
import psutil
|
| 183 |
+
cpu = psutil.cpu_percent(interval=1)
|
| 184 |
+
memory = psutil.virtual_memory()
|
| 185 |
|
| 186 |
+
info_text = f"⚡ استخدام المعالج: {cpu}%\n"
|
| 187 |
+
info_text += f"💾 استخدام الذاكرة: {memory.percent}%\n"
|
| 188 |
+
info_text += f"📡 الخدمات: {'🟢 نشطة' if self.services_running else '🔴 متوقفة'}"
|
| 189 |
+
print(info_text)
|
| 190 |
+
except Exception as e:
|
| 191 |
+
print(f"❌ فشل في الحصول على معلومات النظام: {e}")
|
| 192 |
+
|
| 193 |
+
def restart_services(self, icon=None, item=None):
|
| 194 |
+
"""إعادة تشغيل الخدمات"""
|
| 195 |
+
print("🔄 إعادة تشغيل الخدمات...")
|
| 196 |
+
self.stop_services()
|
| 197 |
+
time.sleep(2)
|
| 198 |
+
self.start_services()
|
| 199 |
+
|
| 200 |
+
def quit_app(self, icon=None, item=None):
|
| 201 |
"""إنهاء التطبيق"""
|
| 202 |
+
print("🛑 إيقاف النظام...")
|
| 203 |
+
self.stop_services()
|
| 204 |
+
if icon:
|
| 205 |
+
icon.stop()
|
| 206 |
+
print("✅ تم إنهاء الت��بيق")
|
| 207 |
+
os._exit(0)
|
| 208 |
|
| 209 |
+
def dummy_action(self, icon=None, item=None):
|
| 210 |
+
"""إجراء افتراضي للفواصل"""
|
| 211 |
+
pass
|
| 212 |
+
|
| 213 |
+
def update_icon_menu(self):
|
| 214 |
"""تحديث قائمة الأيقونة"""
|
| 215 |
if self.icon:
|
| 216 |
+
# تحديث حالة الخدمات
|
| 217 |
+
self.check_service_status()
|
| 218 |
+
|
| 219 |
+
# تحديث لون الأيقونة
|
| 220 |
+
icon_color = 'green' if self.services_running else 'red'
|
| 221 |
+
self.icon.icon = self.create_icon_image(icon_color)
|
| 222 |
|
| 223 |
menu = pystray.Menu(
|
| 224 |
+
item(f'الحالة: {"🟢 نشط" if self.services_running else "🔴 متوقف"}', self.dummy_action, enabled=False),
|
| 225 |
+
item('---', self.dummy_action),
|
| 226 |
+
item('📊 حالة النظام', self.show_status),
|
| 227 |
+
item('💻 معلومات النظام', self.show_system_info),
|
| 228 |
+
item('---', self.dummy_action),
|
| 229 |
+
item('🖥️ فتح الواجهة الرئيسية', self.show_ui),
|
| 230 |
+
item('📈 لوحة التحكم', self.open_dashboard),
|
| 231 |
+
item('⚙️ واجهة المهام', self.open_web_interface),
|
| 232 |
+
item('---', self.dummy_action),
|
| 233 |
+
item('🚀 بدء الخدمات', self.start_services, enabled=not self.services_running),
|
| 234 |
+
item('🛑 إيقاف الخدمات', self.stop_services, enabled=self.services_running),
|
| 235 |
+
item('🔄 إعادة التشغيل', self.restart_services),
|
| 236 |
+
item('---', self.dummy_action),
|
| 237 |
+
item('❌ إنهاء', self.quit_app)
|
| 238 |
)
|
| 239 |
|
| 240 |
self.icon.menu = menu
|
| 241 |
|
| 242 |
def create_menu(self):
|
| 243 |
"""إنشاء قائمة الأيقونة"""
|
| 244 |
+
# فحص الحالة الأولية
|
| 245 |
+
self.check_service_status()
|
| 246 |
+
|
| 247 |
return pystray.Menu(
|
| 248 |
+
item(f'الحالة: {"🟢 نشط" if self.services_running else "🔴 متوقف"}', self.dummy_action, enabled=False),
|
| 249 |
+
item('---', self.dummy_action),
|
| 250 |
+
item('📊 حالة النظام', self.show_status),
|
| 251 |
+
item('💻 معلومات النظام', self.show_system_info),
|
| 252 |
+
item('---', self.dummy_action),
|
| 253 |
+
item('🖥️ فتح الواجهة الرئيسية', self.show_ui),
|
| 254 |
+
item('📈 لوحة التحكم', self.open_dashboard),
|
| 255 |
+
item('⚙️ واجهة المهام', self.open_web_interface),
|
| 256 |
+
item('---', self.dummy_action),
|
| 257 |
+
item('🚀 بدء الخدمات', self.start_services, enabled=not self.services_running),
|
| 258 |
+
item('🛑 إيقاف الخدمات', self.stop_services, enabled=self.services_running),
|
| 259 |
+
item('🔄 إعادة التشغيل', self.restart_services),
|
| 260 |
+
item('---', self.dummy_action),
|
| 261 |
+
item('❌ إنهاء', self.quit_app)
|
| 262 |
)
|
| 263 |
|
| 264 |
def run(self):
|
| 265 |
"""تشغيل أيقونة شريط النظام"""
|
| 266 |
if not TRAY_AVAILABLE:
|
| 267 |
print("❌ مكتبة pystray غير متوفرة")
|
| 268 |
+
print("💡 جرب: pip install pystray Pillow")
|
| 269 |
+
return False
|
| 270 |
|
| 271 |
+
try:
|
| 272 |
+
# إنشاء الأيقونة
|
| 273 |
+
icon_color = 'green' if self.services_running else 'red'
|
| 274 |
+
image = self.create_icon_image(icon_color)
|
| 275 |
+
menu = self.create_menu()
|
| 276 |
+
|
| 277 |
+
self.icon = pystray.Icon(
|
| 278 |
+
"نظام توزيع المهام",
|
| 279 |
+
image,
|
| 280 |
+
menu=menu
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
# تحديث دوري للقائمة
|
| 284 |
+
def update_loop():
|
| 285 |
+
while True:
|
| 286 |
+
time.sleep(10) # تحديث كل 10 ثواني
|
| 287 |
+
if self.icon:
|
| 288 |
+
try:
|
| 289 |
+
self.update_icon_menu()
|
| 290 |
+
except:
|
| 291 |
+
break
|
| 292 |
+
|
| 293 |
+
update_thread = threading.Thread(target=update_loop, daemon=True)
|
| 294 |
+
update_thread.start()
|
| 295 |
+
|
| 296 |
+
print("🖱️ تشغيل أيقونة شريط النظام...")
|
| 297 |
+
print("💡 انقر بزر الماوس الأيمن على الأيقونة للخيارات")
|
| 298 |
+
self.icon.run()
|
| 299 |
+
return True
|
| 300 |
+
|
| 301 |
+
except Exception as e:
|
| 302 |
+
print(f"❌ فشل في تشغيل أيقونة النظام: {e}")
|
| 303 |
+
return False
|
| 304 |
|
| 305 |
def main():
|
| 306 |
+
print("🚀 بدء نظام توزيع المهام...")
|
| 307 |
controller = SystemTrayController()
|
| 308 |
+
|
| 309 |
+
# بدء الخدمات تلقائياً عند التشغيل (اختياري)
|
| 310 |
+
# controller.start_services()
|
| 311 |
+
|
| 312 |
+
success = controller.run()
|
| 313 |
+
if not success:
|
| 314 |
+
print("❌ فشل في تشغيل واجهة النظام")
|
| 315 |
+
input("اضغط Enter للإغلاق...")
|
| 316 |
|
| 317 |
if __name__ == "__main__":
|
| 318 |
main()
|