Upload 25 files
Browse files- autostart_config.py +128 -0
- buildozer.spec +466 -0
- control.py +26 -0
- dashboard.py +55 -0
- distributed_executor.py +117 -0
- dts.service +14 -0
- dts_cli.py +36 -0
- main.py +80 -0
- main.spec +38 -0
- offload_debug.log +15 -0
- offload_lib.py +140 -0
- peer_discovery.py +127 -0
- peer_registry.py +31 -0
- processor_manager.py +47 -0
- remote_executor.py +21 -0
- requirements.txt +5 -0
- rpc_server.py +44 -0
- security_layer.py +70 -0
- server.py +23 -0
- setup.py +24 -0
- setup_fonts.py +20 -0
- startup.py +38 -0
- task_interceptor.py +9 -0
- task_splitter.py +36 -0
- your_tasks.py +113 -0
autostart_config.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
import platform
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
class AutoStartManager:
|
| 7 |
+
def __init__(self, app_name="DistributedTaskSystem"):
|
| 8 |
+
self.app_name = app_name
|
| 9 |
+
self.config_file = Path.home() / f".{app_name}_autostart.json"
|
| 10 |
+
self.load_config()
|
| 11 |
+
|
| 12 |
+
def load_config(self):
|
| 13 |
+
"""تحميل إعدادات التشغيل التلقائي"""
|
| 14 |
+
try:
|
| 15 |
+
with open(self.config_file, 'r') as f:
|
| 16 |
+
self.config = json.load(f)
|
| 17 |
+
except (FileNotFoundError, json.JSONDecodeError):
|
| 18 |
+
self.config = {
|
| 19 |
+
'enabled': False,
|
| 20 |
+
'startup_script': str(Path(__file__).parent / "startup.py")
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
def save_config(self):
|
| 24 |
+
"""حفظ الإعدادات"""
|
| 25 |
+
with open(self.config_file, 'w') as f:
|
| 26 |
+
json.dump(self.config, f, indent=2)
|
| 27 |
+
|
| 28 |
+
def enable_autostart(self):
|
| 29 |
+
"""تفعيل التشغيل التلقائي"""
|
| 30 |
+
self.config['enabled'] = True
|
| 31 |
+
self._setup_autostart()
|
| 32 |
+
self.save_config()
|
| 33 |
+
|
| 34 |
+
def disable_autostart(self):
|
| 35 |
+
"""تعطيل التشغيل التلقائي"""
|
| 36 |
+
self.config['enabled'] = False
|
| 37 |
+
self._remove_autostart()
|
| 38 |
+
self.save_config()
|
| 39 |
+
|
| 40 |
+
def _setup_autostart(self):
|
| 41 |
+
"""إعداد التشغيل التلقائي حسب نظام التشغيل"""
|
| 42 |
+
system = platform.system()
|
| 43 |
+
|
| 44 |
+
if system == "Windows":
|
| 45 |
+
self._setup_windows()
|
| 46 |
+
elif system == "Linux":
|
| 47 |
+
self._setup_linux()
|
| 48 |
+
elif system == "Darwin":
|
| 49 |
+
self._setup_mac()
|
| 50 |
+
|
| 51 |
+
def _setup_windows(self):
|
| 52 |
+
"""إعداد التشغيل التلقائي لـ Windows"""
|
| 53 |
+
import winreg
|
| 54 |
+
key = winreg.OpenKey(
|
| 55 |
+
winreg.HKEY_CURRENT_USER,
|
| 56 |
+
r"Software\Microsoft\Windows\CurrentVersion\Run",
|
| 57 |
+
0, winreg.KEY_SET_VALUE
|
| 58 |
+
)
|
| 59 |
+
winreg.SetValueEx(
|
| 60 |
+
key, self.app_name, 0, winreg.REG_SZ,
|
| 61 |
+
f'python "{self.config["startup_script"]}"'
|
| 62 |
+
)
|
| 63 |
+
winreg.CloseKey(key)
|
| 64 |
+
|
| 65 |
+
def _setup_linux(self):
|
| 66 |
+
"""إعداد التشغيل التلقائي لـ Linux"""
|
| 67 |
+
autostart_dir = Path.home() / ".config/autostart"
|
| 68 |
+
autostart_dir.mkdir(exist_ok=True)
|
| 69 |
+
|
| 70 |
+
desktop_file = autostart_dir / f"{self.app_name}.desktop"
|
| 71 |
+
desktop_file.write_text(f"""
|
| 72 |
+
[Desktop Entry]
|
| 73 |
+
Type=Application
|
| 74 |
+
Name={self.app_name}
|
| 75 |
+
Exec=python3 {self.config['startup_script']}
|
| 76 |
+
Terminal=false
|
| 77 |
+
""")
|
| 78 |
+
|
| 79 |
+
def _setup_mac(self):
|
| 80 |
+
"""إعداد التشغيل التلقائي لـ macOS"""
|
| 81 |
+
plist_dir = Path.home() / "Library/LaunchAgents"
|
| 82 |
+
plist_dir.mkdir(exist_ok=True)
|
| 83 |
+
|
| 84 |
+
plist_file = plist_dir / f"com.{self.app_name.lower()}.plist"
|
| 85 |
+
plist_file.write_text(f"""
|
| 86 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 87 |
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
| 88 |
+
<plist version="1.0">
|
| 89 |
+
<dict>
|
| 90 |
+
<key>Label</key>
|
| 91 |
+
<string>com.{self.app_name.lower()}</string>
|
| 92 |
+
<key>ProgramArguments</key>
|
| 93 |
+
<array>
|
| 94 |
+
<string>python</string>
|
| 95 |
+
<string>{self.config['startup_script']}</string>
|
| 96 |
+
</array>
|
| 97 |
+
<key>RunAtLoad</key>
|
| 98 |
+
<true/>
|
| 99 |
+
</dict>
|
| 100 |
+
</plist>
|
| 101 |
+
""")
|
| 102 |
+
|
| 103 |
+
def _remove_autostart(self):
|
| 104 |
+
"""إزالة التشغيل التلقائي"""
|
| 105 |
+
system = platform.system()
|
| 106 |
+
|
| 107 |
+
if system == "Windows":
|
| 108 |
+
import winreg
|
| 109 |
+
try:
|
| 110 |
+
key = winreg.OpenKey(
|
| 111 |
+
winreg.HKEY_CURRENT_USER,
|
| 112 |
+
r"Software\Microsoft\Windows\CurrentVersion\Run",
|
| 113 |
+
0, winreg.KEY_SET_VALUE
|
| 114 |
+
)
|
| 115 |
+
winreg.DeleteValue(key, self.app_name)
|
| 116 |
+
winreg.CloseKey(key)
|
| 117 |
+
except WindowsError:
|
| 118 |
+
pass
|
| 119 |
+
|
| 120 |
+
elif system == "Linux":
|
| 121 |
+
autostart_file = Path.home() / f".config/autostart/{self.app_name}.desktop"
|
| 122 |
+
if autostart_file.exists():
|
| 123 |
+
autostart_file.unlink()
|
| 124 |
+
|
| 125 |
+
elif system == "Darwin":
|
| 126 |
+
plist_file = Path.home() / f"Library/LaunchAgents/com.{self.app_name.lower()}.plist"
|
| 127 |
+
if plist_file.exists():
|
| 128 |
+
plist_file.unlink()
|
buildozer.spec
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[app]
|
| 2 |
+
|
| 3 |
+
# (str) Title of your application
|
| 4 |
+
# اسم التطبيق
|
| 5 |
+
title = My Offload App
|
| 6 |
+
|
| 7 |
+
# اسم الباكيج (فريد)
|
| 8 |
+
package.name = myoffloadapp
|
| 9 |
+
package.domain = org.osama
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# المتطلبات:
|
| 13 |
+
requirements = python3,kivy,requests,zeroconf,numpy,setuptools
|
| 14 |
+
|
| 15 |
+
# نسخة API
|
| 16 |
+
android.api = 31
|
| 17 |
+
|
| 18 |
+
# الحد الأدنى للأندرويد
|
| 19 |
+
android.minapi = 21
|
| 20 |
+
|
| 21 |
+
# NDK وسكّة البنية
|
| 22 |
+
android.ndk = 25b
|
| 23 |
+
android.sdk = 33
|
| 24 |
+
|
| 25 |
+
# تفعيل الوصول إلى الإنترنت مثلاً
|
| 26 |
+
android.permissions = INTERNET
|
| 27 |
+
|
| 28 |
+
# (str) Package domain (needed for android/ios packaging)
|
| 29 |
+
|
| 30 |
+
# (str) Source code where the main.py live
|
| 31 |
+
source.dir = .
|
| 32 |
+
|
| 33 |
+
# (list) Source files to include (let empty to include all the files)
|
| 34 |
+
|
| 35 |
+
# (list) List of inclusions using pattern matching
|
| 36 |
+
#source.include_patterns = assets/*,images/*.png
|
| 37 |
+
|
| 38 |
+
# (list) Source files to exclude (let empty to not exclude anything)
|
| 39 |
+
#source.exclude_exts = spec
|
| 40 |
+
|
| 41 |
+
# (list) List of directory to exclude (let empty to not exclude anything)
|
| 42 |
+
#source.exclude_dirs = tests, bin, venv
|
| 43 |
+
|
| 44 |
+
# (list) List of exclusions using pattern matching
|
| 45 |
+
# Do not prefix with './'
|
| 46 |
+
#source.exclude_patterns = license,images/*/*.jpg
|
| 47 |
+
|
| 48 |
+
# (str) Application versioning (method 1)
|
| 49 |
+
version = 0.1
|
| 50 |
+
|
| 51 |
+
# (str) Application versioning (method 2)
|
| 52 |
+
# version.regex = __version__ = ['"](.*)['"]
|
| 53 |
+
# version.filename = %(source.dir)s/main.py
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
# (str) Custom source folders for requirements
|
| 57 |
+
# Sets custom source for any requirements with recipes
|
| 58 |
+
# requirements.source.kivy = ../../kivy
|
| 59 |
+
|
| 60 |
+
# (str) Presplash of the application
|
| 61 |
+
#presplash.filename = %(source.dir)s/data/presplash.png
|
| 62 |
+
|
| 63 |
+
# (str) Icon of the application
|
| 64 |
+
#icon.filename = %(source.dir)s/data/icon.png
|
| 65 |
+
|
| 66 |
+
# (list) Supported orientations
|
| 67 |
+
# Valid options are: landscape, portrait, portrait-reverse or landscape-reverse
|
| 68 |
+
orientation = portrait
|
| 69 |
+
|
| 70 |
+
# (list) List of service to declare
|
| 71 |
+
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY
|
| 72 |
+
|
| 73 |
+
#
|
| 74 |
+
# OSX Specific
|
| 75 |
+
#
|
| 76 |
+
|
| 77 |
+
#
|
| 78 |
+
# author = © Copyright Info
|
| 79 |
+
|
| 80 |
+
# change the major version of python used by the app
|
| 81 |
+
osx.python_version = 3
|
| 82 |
+
|
| 83 |
+
# Kivy version to use
|
| 84 |
+
osx.kivy_version = 1.9.1
|
| 85 |
+
|
| 86 |
+
#
|
| 87 |
+
# Android specific
|
| 88 |
+
#
|
| 89 |
+
|
| 90 |
+
# (bool) Indicate if the application should be fullscreen or not
|
| 91 |
+
fullscreen = 0
|
| 92 |
+
|
| 93 |
+
# (string) Presplash background color (for android toolchain)
|
| 94 |
+
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
|
| 95 |
+
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
|
| 96 |
+
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
|
| 97 |
+
# olive, purple, silver, teal.
|
| 98 |
+
#android.presplash_color = #FFFFFF
|
| 99 |
+
|
| 100 |
+
# (string) Presplash animation using Lottie format.
|
| 101 |
+
# see https://lottiefiles.com/ for examples and https://airbnb.design/lottie/
|
| 102 |
+
# for general documentation.
|
| 103 |
+
# Lottie files can be created using various tools, like Adobe After Effect or Synfig.
|
| 104 |
+
#android.presplash_lottie = "path/to/lottie/file.json"
|
| 105 |
+
|
| 106 |
+
# (str) Adaptive icon of the application (used if Android API level is 26+ at runtime)
|
| 107 |
+
#icon.adaptive_foreground.filename = %(source.dir)s/data/icon_fg.png
|
| 108 |
+
#icon.adaptive_background.filename = %(source.dir)s/data/icon_bg.png
|
| 109 |
+
|
| 110 |
+
# (list) Permissions
|
| 111 |
+
# (See https://python-for-android.readthedocs.io/en/latest/buildoptions/#build-options-1 for all the supported syntaxes and properties)
|
| 112 |
+
#android.permissions = android.permission.INTERNET, (name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)
|
| 113 |
+
|
| 114 |
+
# (list) features (adds uses-feature -tags to manifest)
|
| 115 |
+
#android.features = android.hardware.usb.host
|
| 116 |
+
|
| 117 |
+
# (int) Target Android API, should be as high as possible.
|
| 118 |
+
#android.api = 31
|
| 119 |
+
|
| 120 |
+
# (int) Minimum API your APK / AAB will support.
|
| 121 |
+
#android.minapi = 21
|
| 122 |
+
|
| 123 |
+
# (int) Android SDK version to use
|
| 124 |
+
#android.sdk = 20
|
| 125 |
+
|
| 126 |
+
# (str) Android NDK version to use
|
| 127 |
+
#android.ndk = 23b
|
| 128 |
+
|
| 129 |
+
# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
|
| 130 |
+
#android.ndk_api = 21
|
| 131 |
+
|
| 132 |
+
# (bool) Use --private data storage (True) or --dir public storage (False)
|
| 133 |
+
#android.private_storage = True
|
| 134 |
+
|
| 135 |
+
# (str) Android NDK directory (if empty, it will be automatically downloaded.)
|
| 136 |
+
#android.ndk_path =
|
| 137 |
+
|
| 138 |
+
# (str) Android SDK directory (if empty, it will be automatically downloaded.)
|
| 139 |
+
#android.sdk_path =
|
| 140 |
+
|
| 141 |
+
# (str) ANT directory (if empty, it will be automatically downloaded.)
|
| 142 |
+
#android.ant_path =
|
| 143 |
+
|
| 144 |
+
# (bool) If True, then skip trying to update the Android sdk
|
| 145 |
+
# This can be useful to avoid excess Internet downloads or save time
|
| 146 |
+
# when an update is due and you just want to test/build your package
|
| 147 |
+
# android.skip_update = False
|
| 148 |
+
|
| 149 |
+
# (bool) If True, then automatically accept SDK license
|
| 150 |
+
# agreements. This is intended for automation only. If set to False,
|
| 151 |
+
# the default, you will be shown the license when first running
|
| 152 |
+
# buildozer.
|
| 153 |
+
# android.accept_sdk_license = False
|
| 154 |
+
|
| 155 |
+
# (str) Android entry point, default is ok for Kivy-based app
|
| 156 |
+
#android.entrypoint = org.kivy.android.PythonActivity
|
| 157 |
+
|
| 158 |
+
# (str) Full name including package path of the Java class that implements Android Activity
|
| 159 |
+
# use that parameter together with android.entrypoint to set custom Java class instead of PythonActivity
|
| 160 |
+
#android.activity_class_name = org.kivy.android.PythonActivity
|
| 161 |
+
|
| 162 |
+
# (str) Extra xml to write directly inside the <manifest> element of AndroidManifest.xml
|
| 163 |
+
# use that parameter to provide a filename from where to load your custom XML code
|
| 164 |
+
#android.extra_manifest_xml = ./src/android/extra_manifest.xml
|
| 165 |
+
|
| 166 |
+
# (str) Extra xml to write directly inside the <manifest><application> tag of AndroidManifest.xml
|
| 167 |
+
# use that parameter to provide a filename from where to load your custom XML arguments:
|
| 168 |
+
#android.extra_manifest_application_arguments = ./src/android/extra_manifest_application_arguments.xml
|
| 169 |
+
|
| 170 |
+
# (str) Full name including package path of the Java class that implements Python Service
|
| 171 |
+
# use that parameter to set custom Java class which extends PythonService
|
| 172 |
+
#android.service_class_name = org.kivy.android.PythonService
|
| 173 |
+
|
| 174 |
+
# (str) Android app theme, default is ok for Kivy-based app
|
| 175 |
+
# android.apptheme = "@android:style/Theme.NoTitleBar"
|
| 176 |
+
|
| 177 |
+
# (list) Pattern to whitelist for the whole project
|
| 178 |
+
#android.whitelist =
|
| 179 |
+
|
| 180 |
+
# (str) Path to a custom whitelist file
|
| 181 |
+
#android.whitelist_src =
|
| 182 |
+
|
| 183 |
+
# (str) Path to a custom blacklist file
|
| 184 |
+
#android.blacklist_src =
|
| 185 |
+
|
| 186 |
+
# (list) List of Java .jar files to add to the libs so that pyjnius can access
|
| 187 |
+
# their classes. Don't add jars that you do not need, since extra jars can slow
|
| 188 |
+
# down the build process. Allows wildcards matching, for example:
|
| 189 |
+
# OUYA-ODK/libs/*.jar
|
| 190 |
+
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
|
| 191 |
+
|
| 192 |
+
# (list) List of Java files to add to the android project (can be java or a
|
| 193 |
+
# directory containing the files)
|
| 194 |
+
#android.add_src =
|
| 195 |
+
|
| 196 |
+
# (list) Android AAR archives to add
|
| 197 |
+
#android.add_aars =
|
| 198 |
+
|
| 199 |
+
# (list) Put these files or directories in the apk assets directory.
|
| 200 |
+
# Either form may be used, and assets need not be in 'source.include_exts'.
|
| 201 |
+
# 1) android.add_assets = source_asset_relative_path
|
| 202 |
+
# 2) android.add_assets = source_asset_path:destination_asset_relative_path
|
| 203 |
+
#android.add_assets =
|
| 204 |
+
|
| 205 |
+
# (list) Put these files or directories in the apk res directory.
|
| 206 |
+
# The option may be used in three ways, the value may contain one or zero ':'
|
| 207 |
+
# Some examples:
|
| 208 |
+
# 1) A file to add to resources, legal resource names contain ['a-z','0-9','_']
|
| 209 |
+
# android.add_resources = my_icons/all-inclusive.png:drawable/all_inclusive.png
|
| 210 |
+
# 2) A directory, here 'legal_icons' must contain resources of one kind
|
| 211 |
+
# android.add_resources = legal_icons:drawable
|
| 212 |
+
# 3) A directory, here 'legal_resources' must contain one or more directories,
|
| 213 |
+
# each of a resource kind: drawable, xml, etc...
|
| 214 |
+
# android.add_resources = legal_resources
|
| 215 |
+
#android.add_resources =
|
| 216 |
+
|
| 217 |
+
# (list) Gradle dependencies to add
|
| 218 |
+
#android.gradle_dependencies =
|
| 219 |
+
|
| 220 |
+
# (bool) Enable AndroidX support. Enable when 'android.gradle_dependencies'
|
| 221 |
+
# contains an 'androidx' package, or any package from Kotlin source.
|
| 222 |
+
# android.enable_androidx requires android.api >= 28
|
| 223 |
+
#android.enable_androidx = True
|
| 224 |
+
|
| 225 |
+
# (list) add java compile options
|
| 226 |
+
# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
|
| 227 |
+
# see https://developer.android.com/studio/write/java8-support for further information
|
| 228 |
+
# android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"
|
| 229 |
+
|
| 230 |
+
# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
|
| 231 |
+
# please enclose in double quotes
|
| 232 |
+
# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
|
| 233 |
+
#android.add_gradle_repositories =
|
| 234 |
+
|
| 235 |
+
# (list) packaging options to add
|
| 236 |
+
# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
|
| 237 |
+
# can be necessary to solve conflicts in gradle_dependencies
|
| 238 |
+
# please enclose in double quotes
|
| 239 |
+
# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
|
| 240 |
+
#android.add_packaging_options =
|
| 241 |
+
|
| 242 |
+
# (list) Java classes to add as activities to the manifest.
|
| 243 |
+
#android.add_activities = com.example.ExampleActivity
|
| 244 |
+
|
| 245 |
+
# (str) OUYA Console category. Should be one of GAME or APP
|
| 246 |
+
# If you leave this blank, OUYA support will not be enabled
|
| 247 |
+
#android.ouya.category = GAME
|
| 248 |
+
|
| 249 |
+
# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
|
| 250 |
+
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
|
| 251 |
+
|
| 252 |
+
# (str) XML file to include as an intent filters in <activity> tag
|
| 253 |
+
#android.manifest.intent_filters =
|
| 254 |
+
|
| 255 |
+
# (list) Copy these files to src/main/res/xml/ (used for example with intent-filters)
|
| 256 |
+
#android.res_xml = PATH_TO_FILE,
|
| 257 |
+
|
| 258 |
+
# (str) launchMode to set for the main activity
|
| 259 |
+
#android.manifest.launch_mode = standard
|
| 260 |
+
|
| 261 |
+
# (str) screenOrientation to set for the main activity.
|
| 262 |
+
# Valid values can be found at https://developer.android.com/guide/topics/manifest/activity-element
|
| 263 |
+
#android.manifest.orientation = fullSensor
|
| 264 |
+
|
| 265 |
+
# (list) Android additional libraries to copy into libs/armeabi
|
| 266 |
+
#android.add_libs_armeabi = libs/android/*.so
|
| 267 |
+
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
|
| 268 |
+
#android.add_libs_arm64_v8a = libs/android-v8/*.so
|
| 269 |
+
#android.add_libs_x86 = libs/android-x86/*.so
|
| 270 |
+
#android.add_libs_mips = libs/android-mips/*.so
|
| 271 |
+
|
| 272 |
+
# (bool) Indicate whether the screen should stay on
|
| 273 |
+
# Don't forget to add the WAKE_LOCK permission if you set this to True
|
| 274 |
+
#android.wakelock = False
|
| 275 |
+
|
| 276 |
+
# (list) Android application meta-data to set (key=value format)
|
| 277 |
+
#android.meta_data =
|
| 278 |
+
|
| 279 |
+
# (list) Android library project to add (will be added in the
|
| 280 |
+
# project.properties automatically.)
|
| 281 |
+
#android.library_references =
|
| 282 |
+
|
| 283 |
+
# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
|
| 284 |
+
#android.uses_library =
|
| 285 |
+
|
| 286 |
+
# (str) Android logcat filters to use
|
| 287 |
+
#android.logcat_filters = *:S python:D
|
| 288 |
+
|
| 289 |
+
# (bool) Android logcat only display log for activity's pid
|
| 290 |
+
#android.logcat_pid_only = False
|
| 291 |
+
|
| 292 |
+
# (str) Android additional adb arguments
|
| 293 |
+
#android.adb_args = -H host.docker.internal
|
| 294 |
+
|
| 295 |
+
# (bool) Copy library instead of making a libpymodules.so
|
| 296 |
+
#android.copy_libs = 1
|
| 297 |
+
|
| 298 |
+
# (list) The Android archs to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
|
| 299 |
+
# In past, was `android.arch` as we weren't supporting builds for multiple archs at the same time.
|
| 300 |
+
android.archs = arm64-v8a, armeabi-v7a
|
| 301 |
+
|
| 302 |
+
# (int) overrides automatic versionCode computation (used in build.gradle)
|
| 303 |
+
# this is not the same as app version and should only be edited if you know what you're doing
|
| 304 |
+
# android.numeric_version = 1
|
| 305 |
+
|
| 306 |
+
# (bool) enables Android auto backup feature (Android API >=23)
|
| 307 |
+
android.allow_backup = True
|
| 308 |
+
|
| 309 |
+
# (str) XML file for custom backup rules (see official auto backup documentation)
|
| 310 |
+
# android.backup_rules =
|
| 311 |
+
|
| 312 |
+
# (str) If you need to insert variables into your AndroidManifest.xml file,
|
| 313 |
+
# you can do so with the manifestPlaceholders property.
|
| 314 |
+
# This property takes a map of key-value pairs. (via a string)
|
| 315 |
+
# Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"]
|
| 316 |
+
# android.manifest_placeholders = [:]
|
| 317 |
+
|
| 318 |
+
# (bool) Skip byte compile for .py files
|
| 319 |
+
# android.no-byte-compile-python = False
|
| 320 |
+
|
| 321 |
+
# (str) The format used to package the app for release mode (aab or apk or aar).
|
| 322 |
+
# android.release_artifact = aab
|
| 323 |
+
|
| 324 |
+
# (str) The format used to package the app for debug mode (apk or aar).
|
| 325 |
+
# android.debug_artifact = apk
|
| 326 |
+
|
| 327 |
+
#
|
| 328 |
+
# Python for android (p4a) specific
|
| 329 |
+
#
|
| 330 |
+
|
| 331 |
+
# (str) python-for-android URL to use for checkout
|
| 332 |
+
#p4a.url =
|
| 333 |
+
|
| 334 |
+
# (str) python-for-android fork to use in case if p4a.url is not specified, defaults to upstream (kivy)
|
| 335 |
+
#p4a.fork = kivy
|
| 336 |
+
|
| 337 |
+
# (str) python-for-android branch to use, defaults to master
|
| 338 |
+
#p4a.branch = master
|
| 339 |
+
|
| 340 |
+
# (str) python-for-android specific commit to use, defaults to HEAD, must be within p4a.branch
|
| 341 |
+
#p4a.commit = HEAD
|
| 342 |
+
|
| 343 |
+
# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
|
| 344 |
+
#p4a.source_dir =
|
| 345 |
+
|
| 346 |
+
# (str) The directory in which python-for-android should look for your own build recipes (if any)
|
| 347 |
+
#p4a.local_recipes =
|
| 348 |
+
|
| 349 |
+
# (str) Filename to the hook for p4a
|
| 350 |
+
#p4a.hook =
|
| 351 |
+
|
| 352 |
+
# (str) Bootstrap to use for android builds
|
| 353 |
+
# p4a.bootstrap = sdl2
|
| 354 |
+
|
| 355 |
+
# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
|
| 356 |
+
#p4a.port =
|
| 357 |
+
|
| 358 |
+
# Control passing the --use-setup-py vs --ignore-setup-py to p4a
|
| 359 |
+
# "in the future" --use-setup-py is going to be the default behaviour in p4a, right now it is not
|
| 360 |
+
# Setting this to false will pass --ignore-setup-py, true will pass --use-setup-py
|
| 361 |
+
# NOTE: this is general setuptools integration, having pyproject.toml is enough, no need to generate
|
| 362 |
+
# setup.py if you're using Poetry, but you need to add "toml" to source.include_exts.
|
| 363 |
+
#p4a.setup_py = false
|
| 364 |
+
|
| 365 |
+
# (str) extra command line arguments to pass when invoking pythonforandroid.toolchain
|
| 366 |
+
#p4a.extra_args =
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
#
|
| 371 |
+
# iOS specific
|
| 372 |
+
#
|
| 373 |
+
|
| 374 |
+
# (str) Path to a custom kivy-ios folder
|
| 375 |
+
#ios.kivy_ios_dir = ../kivy-ios
|
| 376 |
+
# Alternately, specify the URL and branch of a git checkout:
|
| 377 |
+
ios.kivy_ios_url = https://github.com/kivy/kivy-ios
|
| 378 |
+
ios.kivy_ios_branch = master
|
| 379 |
+
|
| 380 |
+
# Another platform dependency: ios-deploy
|
| 381 |
+
# Uncomment to use a custom checkout
|
| 382 |
+
#ios.ios_deploy_dir = ../ios_deploy
|
| 383 |
+
# Or specify URL and branch
|
| 384 |
+
ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
|
| 385 |
+
ios.ios_deploy_branch = 1.10.0
|
| 386 |
+
|
| 387 |
+
# (bool) Whether or not to sign the code
|
| 388 |
+
ios.codesign.allowed = false
|
| 389 |
+
|
| 390 |
+
# (str) Name of the certificate to use for signing the debug version
|
| 391 |
+
# Get a list of available identities: buildozer ios list_identities
|
| 392 |
+
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
|
| 393 |
+
|
| 394 |
+
# (str) The development team to use for signing the debug version
|
| 395 |
+
#ios.codesign.development_team.debug = <hexstring>
|
| 396 |
+
|
| 397 |
+
# (str) Name of the certificate to use for signing the release version
|
| 398 |
+
#ios.codesign.release = %(ios.codesign.debug)s
|
| 399 |
+
|
| 400 |
+
# (str) The development team to use for signing the release version
|
| 401 |
+
#ios.codesign.development_team.release = <hexstring>
|
| 402 |
+
|
| 403 |
+
# (str) URL pointing to .ipa file to be installed
|
| 404 |
+
# This option should be defined along with `display_image_url` and `full_size_image_url` options.
|
| 405 |
+
#ios.manifest.app_url =
|
| 406 |
+
|
| 407 |
+
# (str) URL pointing to an icon (57x57px) to be displayed during download
|
| 408 |
+
# This option should be defined along with `app_url` and `full_size_image_url` options.
|
| 409 |
+
#ios.manifest.display_image_url =
|
| 410 |
+
|
| 411 |
+
# (str) URL pointing to a large icon (512x512px) to be used by iTunes
|
| 412 |
+
# This option should be defined along with `app_url` and `display_image_url` options.
|
| 413 |
+
#ios.manifest.full_size_image_url =
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
[buildozer]
|
| 417 |
+
|
| 418 |
+
# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
|
| 419 |
+
log_level = 2
|
| 420 |
+
|
| 421 |
+
# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
|
| 422 |
+
warn_on_root = 1
|
| 423 |
+
|
| 424 |
+
# (str) Path to build artifact storage, absolute or relative to spec file
|
| 425 |
+
# build_dir = ./.buildozer
|
| 426 |
+
|
| 427 |
+
# (str) Path to build output (i.e. .apk, .aab, .ipa) storage
|
| 428 |
+
# bin_dir = ./bin
|
| 429 |
+
|
| 430 |
+
# -----------------------------------------------------------------------------
|
| 431 |
+
# List as sections
|
| 432 |
+
#
|
| 433 |
+
# You can define all the "list" as [section:key].
|
| 434 |
+
# Each line will be considered as a option to the list.
|
| 435 |
+
# Let's take [app] / source.exclude_patterns.
|
| 436 |
+
# Instead of doing:
|
| 437 |
+
#
|
| 438 |
+
#[app]
|
| 439 |
+
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
|
| 440 |
+
#
|
| 441 |
+
# This can be translated into:
|
| 442 |
+
#
|
| 443 |
+
#[app:source.exclude_patterns]
|
| 444 |
+
#license
|
| 445 |
+
#data/audio/*.wav
|
| 446 |
+
#data/images/original/*
|
| 447 |
+
#
|
| 448 |
+
|
| 449 |
+
|
| 450 |
+
# -----------------------------------------------------------------------------
|
| 451 |
+
# Profiles
|
| 452 |
+
#
|
| 453 |
+
# You can extend section / key with a profile
|
| 454 |
+
# For example, you want to deploy a demo version of your application without
|
| 455 |
+
# HD content. You could first change the title to add "(demo)" in the name
|
| 456 |
+
# and extend the excluded directories to remove the HD content.
|
| 457 |
+
#
|
| 458 |
+
#[app@demo]
|
| 459 |
+
#title = My Application (demo)
|
| 460 |
+
#
|
| 461 |
+
#[app:source.exclude_patterns@demo]
|
| 462 |
+
#images/hd/*
|
| 463 |
+
#
|
| 464 |
+
# Then, invoke the command line with the "demo" profile:
|
| 465 |
+
#
|
| 466 |
+
#buildozer --profile demo android debug
|
control.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
from autostart_config import AutoStartManager
|
| 3 |
+
|
| 4 |
+
def main():
|
| 5 |
+
parser = argparse.ArgumentParser(description="نظام التحكم في التشغيل التلقائي")
|
| 6 |
+
parser.add_argument('--enable', action='store_true', help="تفعيل التشغيل التلقائي")
|
| 7 |
+
parser.add_argument('--disable', action='store_true', help="تعطيل التشغيل التلقائي")
|
| 8 |
+
parser.add_argument('--status', action='store_true', help="عرض حالة التشغيل التلقائي")
|
| 9 |
+
|
| 10 |
+
args = parser.parse_args()
|
| 11 |
+
manager = AutoStartManager()
|
| 12 |
+
|
| 13 |
+
if args.enable:
|
| 14 |
+
manager.enable_autostart()
|
| 15 |
+
print("✓ تم تفعيل التشغيل التلقائي")
|
| 16 |
+
elif args.disable:
|
| 17 |
+
manager.disable_autostart()
|
| 18 |
+
print("✗ تم تعطيل التشغيل التلقائي")
|
| 19 |
+
elif args.status:
|
| 20 |
+
status = "مفعل" if manager.config['enabled'] else "معطل"
|
| 21 |
+
print(f"حالة التشغيل التلقائي: {status}")
|
| 22 |
+
else:
|
| 23 |
+
parser.print_help()
|
| 24 |
+
|
| 25 |
+
if __name__ == "__main__":
|
| 26 |
+
main()
|
dashboard.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# dashboard.py
|
| 2 |
+
from flask import Flask, render_template, jsonify
|
| 3 |
+
from peer_discovery import discover_peers
|
| 4 |
+
import threading
|
| 5 |
+
import time
|
| 6 |
+
from typing import List, Dict
|
| 7 |
+
import logging
|
| 8 |
+
|
| 9 |
+
app = Flask(__name__)
|
| 10 |
+
app.logger.setLevel(logging.INFO)
|
| 11 |
+
|
| 12 |
+
# تهيئة قائمة الأقران
|
| 13 |
+
current_peers: Dict[str, List[Dict[str, str]]] = {"local": [], "external": []}
|
| 14 |
+
|
| 15 |
+
def update_peers_loop() -> None:
|
| 16 |
+
"""حلقة تحديث قائمة الأقران بشكل دوري"""
|
| 17 |
+
global current_peers
|
| 18 |
+
while True:
|
| 19 |
+
try:
|
| 20 |
+
new_peers = discover_peers()
|
| 21 |
+
current_peers = new_peers
|
| 22 |
+
total_peers = len(new_peers["local"]) + len(new_peers["external"])
|
| 23 |
+
app.logger.info(f"تم تحديث قائمة الأقران: {total_peers} جهاز")
|
| 24 |
+
except Exception as e:
|
| 25 |
+
app.logger.error(f"خطأ في اكتشاف الأقران: {str(e)}")
|
| 26 |
+
time.sleep(10)
|
| 27 |
+
|
| 28 |
+
@app.route("/")
|
| 29 |
+
def dashboard() -> str:
|
| 30 |
+
"""عرض لوحة التحكم الرئيسية"""
|
| 31 |
+
total_peers = len(current_peers["local"]) + len(current_peers["external"])
|
| 32 |
+
return render_template("dashboard.html",
|
| 33 |
+
peers_count=total_peers,
|
| 34 |
+
last_update=time.strftime("%Y-%m-%d %H:%M:%S"))
|
| 35 |
+
|
| 36 |
+
@app.route("/api/peers")
|
| 37 |
+
def get_peers() -> dict:
|
| 38 |
+
"""واجهة API للحصول على قائمة الأقران"""
|
| 39 |
+
total_peers = len(current_peers["local"]) + len(current_peers["external"])
|
| 40 |
+
return jsonify({
|
| 41 |
+
"peers": current_peers,
|
| 42 |
+
"count": total_peers,
|
| 43 |
+
"status": "success"
|
| 44 |
+
})
|
| 45 |
+
|
| 46 |
+
def start_background_thread() -> None:
|
| 47 |
+
"""بدء خيط الخلفية لتحديث الأقران"""
|
| 48 |
+
thread = threading.Thread(target=update_peers_loop)
|
| 49 |
+
thread.daemon = True
|
| 50 |
+
thread.start()
|
| 51 |
+
|
| 52 |
+
if __name__ == "__main__":
|
| 53 |
+
start_background_thread()
|
| 54 |
+
app.run(host="0.0.0.0", port=7530, debug=False)
|
| 55 |
+
|
distributed_executor.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# distributed_executor.py
|
| 2 |
+
import threading
|
| 3 |
+
import queue
|
| 4 |
+
import time
|
| 5 |
+
import json
|
| 6 |
+
from typing import Callable, Dict, List
|
| 7 |
+
import socket
|
| 8 |
+
from zeroconf import Zeroconf, ServiceBrowser, ServiceInfo
|
| 9 |
+
import logging
|
| 10 |
+
|
| 11 |
+
class PeerRegistry:
|
| 12 |
+
def __init__(self):
|
| 13 |
+
self._peers = {}
|
| 14 |
+
self._zeroconf = Zeroconf()
|
| 15 |
+
self.local_node_id = socket.gethostname()
|
| 16 |
+
|
| 17 |
+
def register_service(self, name: str, port: int, load: float = 0.0):
|
| 18 |
+
service_info = ServiceInfo(
|
| 19 |
+
"_tasknode._tcp.local.",
|
| 20 |
+
f"{name}._tasknode._tcp.local.",
|
| 21 |
+
addresses=[socket.inet_aton(self._get_local_ip())],
|
| 22 |
+
port=port,
|
| 23 |
+
properties={'load': str(load), 'node_id': self.local_node_id},
|
| 24 |
+
server=f"{name}.local."
|
| 25 |
+
)
|
| 26 |
+
self._zeroconf.register_service(service_info)
|
| 27 |
+
|
| 28 |
+
def discover_peers(self, timeout: int = 3) -> List[Dict]:
|
| 29 |
+
class Listener:
|
| 30 |
+
def __init__(self):
|
| 31 |
+
self.peers = []
|
| 32 |
+
|
| 33 |
+
def add_service(self, zc, type_, name):
|
| 34 |
+
info = zc.get_service_info(type_, name)
|
| 35 |
+
if info:
|
| 36 |
+
ip = socket.inet_ntoa(info.addresses[0])
|
| 37 |
+
self.peers.append({
|
| 38 |
+
'ip': ip,
|
| 39 |
+
'port': info.port,
|
| 40 |
+
'load': float(info.properties[b'load']),
|
| 41 |
+
'node_id': info.properties[b'node_id'].decode(),
|
| 42 |
+
'last_seen': time.time()
|
| 43 |
+
})
|
| 44 |
+
|
| 45 |
+
listener = Listener()
|
| 46 |
+
browser = ServiceBrowser(self._zeroconf, "_tasknode._tcp.local.", listener)
|
| 47 |
+
time.sleep(timeout)
|
| 48 |
+
return sorted(listener.peers, key=lambda x: x['load'])
|
| 49 |
+
|
| 50 |
+
def _get_local_ip(self) -> str:
|
| 51 |
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
| 52 |
+
try:
|
| 53 |
+
s.connect(('10.255.255.255', 1))
|
| 54 |
+
ip = s.getsockname()[0]
|
| 55 |
+
except:
|
| 56 |
+
ip = '127.0.0.1'
|
| 57 |
+
finally:
|
| 58 |
+
s.close()
|
| 59 |
+
return ip
|
| 60 |
+
|
| 61 |
+
class DistributedExecutor:
|
| 62 |
+
def __init__(self, shared_secret: str):
|
| 63 |
+
self.peer_registry = PeerRegistry()
|
| 64 |
+
self.shared_secret = shared_secret
|
| 65 |
+
self.task_queue = queue.PriorityQueue()
|
| 66 |
+
self.result_cache = {}
|
| 67 |
+
self._init_peer_discovery()
|
| 68 |
+
logging.basicConfig(level=logging.INFO)
|
| 69 |
+
|
| 70 |
+
def _init_peer_discovery(self):
|
| 71 |
+
def discovery_loop():
|
| 72 |
+
while True:
|
| 73 |
+
self.available_peers = self.peer_registry.discover_peers()
|
| 74 |
+
time.sleep(10)
|
| 75 |
+
|
| 76 |
+
threading.Thread(target=discovery_loop, daemon=True).start()
|
| 77 |
+
|
| 78 |
+
def submit(self, task_func: Callable, *args, **kwargs):
|
| 79 |
+
"""إرسال مهمة جديدة للنظام"""
|
| 80 |
+
task_id = f"{task_func.__name__}_{time.time()}"
|
| 81 |
+
|
| 82 |
+
# تجهيز المهمة
|
| 83 |
+
task = {
|
| 84 |
+
'task_id': task_id,
|
| 85 |
+
'function': task_func.__name__,
|
| 86 |
+
'args': args,
|
| 87 |
+
'kwargs': kwargs,
|
| 88 |
+
'sender_id': self.peer_registry.local_node_id
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
# إرسال المهمة (تبسيط الإرسال في هذا المثال)
|
| 92 |
+
if self.available_peers:
|
| 93 |
+
peer = min(self.available_peers, key=lambda x: x['load'])
|
| 94 |
+
self._send_to_peer(peer, task)
|
| 95 |
+
else:
|
| 96 |
+
logging.warning("لا توجد أجهزة متاحة - سيتم تنفيذ المهمة محلياً")
|
| 97 |
+
|
| 98 |
+
def _send_to_peer(self, peer: Dict, task: Dict):
|
| 99 |
+
"""إرسال مهمة إلى جهاز آخر"""
|
| 100 |
+
try:
|
| 101 |
+
url = f"http://{peer['ip']}:{peer['port']}/run"
|
| 102 |
+
response = requests.post(
|
| 103 |
+
url,
|
| 104 |
+
json=task,
|
| 105 |
+
timeout=10
|
| 106 |
+
)
|
| 107 |
+
response.raise_for_status()
|
| 108 |
+
return response.json()
|
| 109 |
+
except Exception as e:
|
| 110 |
+
logging.error(f"فشل إرسال المهمة لـ {peer['node_id']}: {str(e)}")
|
| 111 |
+
return None
|
| 112 |
+
|
| 113 |
+
# نموذج استخدام مبسط
|
| 114 |
+
if __name__ == "__main__":
|
| 115 |
+
executor = DistributedExecutor("my_secret_key")
|
| 116 |
+
executor.peer_registry.register_service("node1", 7520, load=0.1)
|
| 117 |
+
print("نظام توزيع المهام جاهز...")
|
dts.service
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[Unit]
|
| 2 |
+
Description=Distributed Task System
|
| 3 |
+
After=network.target
|
| 4 |
+
|
| 5 |
+
[Service]
|
| 6 |
+
ExecStart=/usr/local/bin/dts-start
|
| 7 |
+
Restart=always
|
| 8 |
+
User=root
|
| 9 |
+
Group=root
|
| 10 |
+
Environment=PATH=/usr/bin:/usr/local/bin
|
| 11 |
+
Environment=PYTHONUNBUFFERED=1
|
| 12 |
+
|
| 13 |
+
[Install]
|
| 14 |
+
WantedBy=multi-user.target
|
dts_cli.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# dts_cli.py
|
| 2 |
+
import click
|
| 3 |
+
from dashboard import app
|
| 4 |
+
from rpc_server import app as rpc_app
|
| 5 |
+
import threading
|
| 6 |
+
|
| 7 |
+
@click.group()
|
| 8 |
+
def cli():
|
| 9 |
+
pass
|
| 10 |
+
|
| 11 |
+
@cli.command()
|
| 12 |
+
def start():
|
| 13 |
+
"""بدء النظام الموزع"""
|
| 14 |
+
print("جارِ تشغيل النظام الموزع...")
|
| 15 |
+
|
| 16 |
+
# تشغيل واجهة التحكم في خيط منفصل
|
| 17 |
+
dashboard_thread = threading.Thread(
|
| 18 |
+
target=lambda: app.run(host="0.0.0.0", port=5000)
|
| 19 |
+
)
|
| 20 |
+
dashboard_thread.daemon = True
|
| 21 |
+
dashboard_thread.start()
|
| 22 |
+
|
| 23 |
+
# تشغيل خادم RPC
|
| 24 |
+
rpc_app.run(host="0.0.0.0", port=7520)
|
| 25 |
+
|
| 26 |
+
@cli.command()
|
| 27 |
+
def discover():
|
| 28 |
+
"""عرض الأجهزة المتصلة"""
|
| 29 |
+
from peer_discovery import discover_peers
|
| 30 |
+
peers = discover_peers()
|
| 31 |
+
print("الأجهزة المتصلة:")
|
| 32 |
+
for i, peer in enumerate(peers, 1):
|
| 33 |
+
print(f"{i}. {peer}")
|
| 34 |
+
|
| 35 |
+
if __name__ == "__main__":
|
| 36 |
+
cli()
|
main.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# main.py
|
| 2 |
+
import time
|
| 3 |
+
import json
|
| 4 |
+
from distributed_executor import DistributedExecutor
|
| 5 |
+
from your_tasks import *
|
| 6 |
+
# main.py
|
| 7 |
+
from distributed_executor import DistributedExecutor
|
| 8 |
+
import logging
|
| 9 |
+
|
| 10 |
+
def main():
|
| 11 |
+
logging.basicConfig(level=logging.INFO)
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
# تهيئة النظام
|
| 15 |
+
executor = DistributedExecutor("my_shared_secret_123")
|
| 16 |
+
executor.peer_registry.register_service("main_node", 7520)
|
| 17 |
+
|
| 18 |
+
logging.info("نظام توزيع المهام يعمل...")
|
| 19 |
+
|
| 20 |
+
# يمكنك هنا إضافة مهام للتنفيذ
|
| 21 |
+
while True:
|
| 22 |
+
time.sleep(1)
|
| 23 |
+
|
| 24 |
+
except Exception as e:
|
| 25 |
+
logging.error(f"خطأ رئيسي: {str(e)}")
|
| 26 |
+
|
| 27 |
+
if __name__ == "__main__":
|
| 28 |
+
main()
|
| 29 |
+
def example_task(x):
|
| 30 |
+
# مهمة معقدة قابلة للتوزيع
|
| 31 |
+
return x * x + complex_operation(x)
|
| 32 |
+
|
| 33 |
+
def benchmark(task_func, *args):
|
| 34 |
+
"""قياس أداء المهمة"""
|
| 35 |
+
start = time.time()
|
| 36 |
+
result = task_func(*args)
|
| 37 |
+
duration = time.time() - start
|
| 38 |
+
return duration, result
|
| 39 |
+
|
| 40 |
+
def main():
|
| 41 |
+
executor = DistributedExecutor("my_shared_secret_123")
|
| 42 |
+
executor.peer_registry.register_service("node1", 7520, load=0.2)
|
| 43 |
+
|
| 44 |
+
tasks = {
|
| 45 |
+
"1": ("ضرب المصفوفات", matrix_multiply, 500),
|
| 46 |
+
"2": ("حساب الأعداد الأولية", prime_calculation, 100000),
|
| 47 |
+
"3": ("معالجة البيانات", data_processing, 10000),
|
| 48 |
+
"4": ("محاكاة معالجة الصور", image_processing_emulation, 100),
|
| 49 |
+
"5": ("مهمة موزعة معقدة", example_task, 42)
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
while True:
|
| 53 |
+
print("\nنظام توزيع المهام الذكي")
|
| 54 |
+
print("اختر مهمة لتشغيلها:")
|
| 55 |
+
for k, v in tasks.items():
|
| 56 |
+
print(f"{k}: {v[0]}")
|
| 57 |
+
choice = input("اختر المهمة (أو 'q' للخروج): ")
|
| 58 |
+
|
| 59 |
+
if choice.lower() == 'q':
|
| 60 |
+
break
|
| 61 |
+
|
| 62 |
+
if choice in tasks:
|
| 63 |
+
name, func, arg = tasks[choice]
|
| 64 |
+
print(f"\nتشغيل: {name}...")
|
| 65 |
+
|
| 66 |
+
if choice == "5":
|
| 67 |
+
print("تم إرسال المهمة إلى العقدة الموزعة...")
|
| 68 |
+
future = executor.submit(func, arg)
|
| 69 |
+
result = future.result()
|
| 70 |
+
print(f"النتيجة (موزعة): {result}")
|
| 71 |
+
else:
|
| 72 |
+
duration, result = benchmark(func, arg)
|
| 73 |
+
print(f"النتيجة: {json.dumps(result, indent=2)[:200]}...")
|
| 74 |
+
print(f"الوقت المستغرق: {duration:.2f} ثانية")
|
| 75 |
+
else:
|
| 76 |
+
print("اختيار غير صحيح!")
|
| 77 |
+
|
| 78 |
+
if __name__ == "__main__":
|
| 79 |
+
main()
|
| 80 |
+
|
main.spec
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- mode: python ; coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
a = Analysis(
|
| 5 |
+
['main.py'],
|
| 6 |
+
pathex=[],
|
| 7 |
+
binaries=[],
|
| 8 |
+
datas=[],
|
| 9 |
+
hiddenimports=[],
|
| 10 |
+
hookspath=[],
|
| 11 |
+
hooksconfig={},
|
| 12 |
+
runtime_hooks=[],
|
| 13 |
+
excludes=[],
|
| 14 |
+
noarchive=False,
|
| 15 |
+
optimize=0,
|
| 16 |
+
)
|
| 17 |
+
pyz = PYZ(a.pure)
|
| 18 |
+
|
| 19 |
+
exe = EXE(
|
| 20 |
+
pyz,
|
| 21 |
+
a.scripts,
|
| 22 |
+
a.binaries,
|
| 23 |
+
a.datas,
|
| 24 |
+
[],
|
| 25 |
+
name='main',
|
| 26 |
+
debug=False,
|
| 27 |
+
bootloader_ignore_signals=False,
|
| 28 |
+
strip=False,
|
| 29 |
+
upx=True,
|
| 30 |
+
upx_exclude=[],
|
| 31 |
+
runtime_tmpdir=None,
|
| 32 |
+
console=False,
|
| 33 |
+
disable_windowed_traceback=False,
|
| 34 |
+
argv_emulation=False,
|
| 35 |
+
target_arch=None,
|
| 36 |
+
codesign_identity=None,
|
| 37 |
+
entitlements_file=None,
|
| 38 |
+
)
|
offload_debug.log
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
2025-06-25 08:58:55,491 - INFO - [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
| 2 |
+
* Running on all addresses (0.0.0.0)
|
| 3 |
+
* Running on http://127.0.0.1:7520
|
| 4 |
+
* Running on http://192.168.100.37:7520
|
| 5 |
+
2025-06-25 08:58:55,492 - INFO - [33mPress CTRL+C to quit[0m
|
| 6 |
+
2025-06-25 09:05:53,645 - INFO - [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
| 7 |
+
* Running on all addresses (0.0.0.0)
|
| 8 |
+
* Running on http://127.0.0.1:7520
|
| 9 |
+
* Running on http://192.168.100.37:7520
|
| 10 |
+
2025-06-25 09:05:53,647 - INFO - [33mPress CTRL+C to quit[0m
|
| 11 |
+
2025-06-25 10:17:35,364 - INFO - [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
| 12 |
+
* Running on all addresses (0.0.0.0)
|
| 13 |
+
* Running on http://127.0.0.1:7520
|
| 14 |
+
* Running on http://192.168.100.37:7520
|
| 15 |
+
2025-06-25 10:17:35,365 - INFO - [33mPress CTRL+C to quit[0m
|
offload_lib.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# offload_lib.py
|
| 2 |
+
import time
|
| 3 |
+
import math
|
| 4 |
+
import random
|
| 5 |
+
import psutil
|
| 6 |
+
import requests
|
| 7 |
+
import socket
|
| 8 |
+
from functools import wraps
|
| 9 |
+
from zeroconf import Zeroconf, ServiceBrowser
|
| 10 |
+
import logging
|
| 11 |
+
|
| 12 |
+
# إعداد السجل
|
| 13 |
+
logging.basicConfig(
|
| 14 |
+
level=logging.INFO,
|
| 15 |
+
format='%(asctime)s - %(levelname)s - %(message)s'
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
# إعدادات التحميل
|
| 19 |
+
MAX_CPU = 0.6 # عتبة استخدام CPU
|
| 20 |
+
MIN_MEM = 500 # الحد الأدنى للذاكرة (MB)
|
| 21 |
+
|
| 22 |
+
class PeerListener:
|
| 23 |
+
def __init__(self):
|
| 24 |
+
self.peers = []
|
| 25 |
+
|
| 26 |
+
def add_service(self, zc, type, name):
|
| 27 |
+
info = zc.get_service_info(type, name)
|
| 28 |
+
if info:
|
| 29 |
+
ip = socket.inet_ntoa(info.addresses[0])
|
| 30 |
+
self.peers.append(f"{ip}:{info.port}")
|
| 31 |
+
|
| 32 |
+
def discover_peers(timeout=1.5):
|
| 33 |
+
"""اكتشاف الأجهزة المتاحة على الشبكة"""
|
| 34 |
+
zc = Zeroconf()
|
| 35 |
+
listener = PeerListener()
|
| 36 |
+
ServiceBrowser(zc, "_http._tcp.local.", listener)
|
| 37 |
+
time.sleep(timeout)
|
| 38 |
+
zc.close()
|
| 39 |
+
return listener.peers
|
| 40 |
+
|
| 41 |
+
def try_offload(peer, payload, max_retries=3):
|
| 42 |
+
"""محاولة إرسال المهمة إلى جهاز آخر"""
|
| 43 |
+
url = f"http://{peer}/run"
|
| 44 |
+
for attempt in range(max_retries):
|
| 45 |
+
try:
|
| 46 |
+
response = requests.post(url, json=payload, timeout=10)
|
| 47 |
+
response.raise_for_status()
|
| 48 |
+
return response.json()
|
| 49 |
+
except Exception as e:
|
| 50 |
+
logging.warning(f"فشل المحاولة {attempt + 1} لـ {peer}: {str(e)}")
|
| 51 |
+
time.sleep(0.5 * (attempt + 1))
|
| 52 |
+
raise ConnectionError(f"فشل جميع المحاولات لـ {peer}")
|
| 53 |
+
|
| 54 |
+
def estimate_complexity(func, args, kwargs):
|
| 55 |
+
"""تقدير تعقيد المهمة"""
|
| 56 |
+
if func.__name__ == "matrix_multiply":
|
| 57 |
+
return args[0] ** 2
|
| 58 |
+
elif func.__name__ == "prime_calculation":
|
| 59 |
+
return args[0] / 100
|
| 60 |
+
elif func.__name__ == "data_processing":
|
| 61 |
+
return args[0] / 10
|
| 62 |
+
elif func.__name__ == "image_processing_emulation":
|
| 63 |
+
return args[0] * 5
|
| 64 |
+
return 1 # قيمة افتراضية
|
| 65 |
+
|
| 66 |
+
def offload(func):
|
| 67 |
+
"""ديكوراتور لتوزيع المهام"""
|
| 68 |
+
@wraps(func)
|
| 69 |
+
def wrapper(*args, **kwargs):
|
| 70 |
+
# حساب الحمل الحالي
|
| 71 |
+
cpu = psutil.cpu_percent(interval=0.5) / 100.0
|
| 72 |
+
mem = psutil.virtual_memory().available / (1024**2)
|
| 73 |
+
complexity = estimate_complexity(func, args, kwargs)
|
| 74 |
+
|
| 75 |
+
logging.info(f"حمل النظام - CPU: {cpu:.2f}, الذاكرة: {mem:.1f}MB, تعقيد المهمة: {complexity}")
|
| 76 |
+
|
| 77 |
+
# اتخاذ قرار التوزيع
|
| 78 |
+
if complexity > 50 or cpu > MAX_CPU or mem < MIN_MEM:
|
| 79 |
+
try:
|
| 80 |
+
peers = discover_peers()
|
| 81 |
+
if peers:
|
| 82 |
+
payload = {
|
| 83 |
+
"func": func.__name__,
|
| 84 |
+
"args": args,
|
| 85 |
+
"kwargs": kwargs,
|
| 86 |
+
"complexity": complexity
|
| 87 |
+
}
|
| 88 |
+
selected_peer = random.choice(peers)
|
| 89 |
+
logging.info(f"إرسال المهمة إلى {selected_peer}")
|
| 90 |
+
return try_offload(selected_peer, payload)
|
| 91 |
+
except Exception as e:
|
| 92 |
+
logging.error(f"خطأ في التوزيع: {str(e)}")
|
| 93 |
+
|
| 94 |
+
# التنفيذ المحلي إذا فشل التوزيع
|
| 95 |
+
logging.info("تنفيذ المهمة محلياً")
|
| 96 |
+
return func(*args, **kwargs)
|
| 97 |
+
return wrapper
|
| 98 |
+
|
| 99 |
+
# المهام القابلة للتوزيع:
|
| 100 |
+
|
| 101 |
+
@offload
|
| 102 |
+
def matrix_multiply(size):
|
| 103 |
+
"""ضرب مصفوفات كبيرة"""
|
| 104 |
+
import numpy as np
|
| 105 |
+
A = np.random.rand(size, size)
|
| 106 |
+
B = np.random.rand(size, size)
|
| 107 |
+
return {"result": np.dot(A, B).tolist()}
|
| 108 |
+
|
| 109 |
+
@offload
|
| 110 |
+
def prime_calculation(n):
|
| 111 |
+
"""حساب الأعداد الأولية"""
|
| 112 |
+
primes = []
|
| 113 |
+
for num in range(2, n + 1):
|
| 114 |
+
is_prime = True
|
| 115 |
+
for i in range(2, int(math.sqrt(num)) + 1):
|
| 116 |
+
if num % i == 0:
|
| 117 |
+
is_prime = False
|
| 118 |
+
break
|
| 119 |
+
if is_prime:
|
| 120 |
+
primes.append(num)
|
| 121 |
+
return {"primes_count": len(primes), "primes": primes}
|
| 122 |
+
|
| 123 |
+
@offload
|
| 124 |
+
def data_processing(data_size):
|
| 125 |
+
"""معالجة بيانات كبيرة"""
|
| 126 |
+
processed_data = []
|
| 127 |
+
for i in range(data_size):
|
| 128 |
+
result = sum(math.sin(x) * math.cos(x) for x in range(i, i + 100))
|
| 129 |
+
processed_data.append(result)
|
| 130 |
+
return {"processed_items": len(processed_data)}
|
| 131 |
+
|
| 132 |
+
@offload
|
| 133 |
+
def image_processing_emulation(iterations):
|
| 134 |
+
"""محاكاة معالجة الصور"""
|
| 135 |
+
results = []
|
| 136 |
+
for i in range(iterations):
|
| 137 |
+
fake_processing = sum(math.sqrt(x) for x in range(i * 100, (i + 1) * 100))
|
| 138 |
+
results.append(fake_processing)
|
| 139 |
+
time.sleep(0.01)
|
| 140 |
+
return {"iterations": iterations, "results": results}
|
peer_discovery.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import socket
|
| 2 |
+
import time
|
| 3 |
+
import requests
|
| 4 |
+
from zeroconf import Zeroconf, ServiceBrowser
|
| 5 |
+
from typing import List, Dict, Optional
|
| 6 |
+
import urllib3
|
| 7 |
+
import logging
|
| 8 |
+
from dataclasses import dataclass
|
| 9 |
+
|
| 10 |
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
| 11 |
+
|
| 12 |
+
# إعداد التسجيل
|
| 13 |
+
logging.basicConfig(level=logging.INFO)
|
| 14 |
+
logger = logging.getLogger(__name__)
|
| 15 |
+
|
| 16 |
+
@dataclass
|
| 17 |
+
class Device:
|
| 18 |
+
name: str
|
| 19 |
+
ip: str
|
| 20 |
+
port: Optional[int] = None
|
| 21 |
+
device_type: str = "unknown"
|
| 22 |
+
last_seen: float = time.time()
|
| 23 |
+
|
| 24 |
+
class NetworkDiscoverer:
|
| 25 |
+
def __init__(self):
|
| 26 |
+
self.local_devices: List[Device] = []
|
| 27 |
+
self.external_devices: List[Device] = []
|
| 28 |
+
self.central_server_urls = [
|
| 29 |
+
"https://89.111.171.92:1500/api/discover",
|
| 30 |
+
"https://backup-server.example.com:1500/api/discover"
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
+
def discover_all_devices(self, timeout: int = 3) -> Dict[str, List[Device]]:
|
| 34 |
+
"""اكتشاف جميع الأجهزة (محلية وخارجية)"""
|
| 35 |
+
self._discover_local_devices(timeout)
|
| 36 |
+
self._discover_external_devices()
|
| 37 |
+
return {
|
| 38 |
+
"local": self.local_devices,
|
| 39 |
+
"external": self.external_devices
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
def _discover_local_devices(self, timeout: int):
|
| 43 |
+
"""اكتشاف الأجهزة على الشبكة المحلية"""
|
| 44 |
+
try:
|
| 45 |
+
zc = Zeroconf()
|
| 46 |
+
listener = self.LocalListener(self)
|
| 47 |
+
browser = ServiceBrowser(zc, "_tasknode._tcp.local.", listener)
|
| 48 |
+
time.sleep(timeout)
|
| 49 |
+
zc.close()
|
| 50 |
+
except Exception as e:
|
| 51 |
+
logger.error(f"فشل الاكتشاف المحلي: {e}")
|
| 52 |
+
|
| 53 |
+
def _discover_external_devices(self):
|
| 54 |
+
"""اكتشاف الأجهزة عبر الخوادم المركزية"""
|
| 55 |
+
for server_url in self.central_server_urls:
|
| 56 |
+
try:
|
| 57 |
+
response = requests.get(server_url, timeout=5, verify=False)
|
| 58 |
+
if response.ok:
|
| 59 |
+
self._process_external_devices(response.json())
|
| 60 |
+
except Exception as e:
|
| 61 |
+
logger.warning(f"فشل الاتصال بالخادم {server_url}: {e}")
|
| 62 |
+
|
| 63 |
+
def _process_external_devices(self, devices_data):
|
| 64 |
+
"""معالجة بيانات الأجهزة الخارجية"""
|
| 65 |
+
for device_data in devices_data.get('nodes', []):
|
| 66 |
+
if not self._is_duplicate_device(device_data):
|
| 67 |
+
device = Device(
|
| 68 |
+
name=device_data.get('name', 'غير معروف'),
|
| 69 |
+
ip=device_data.get('ip'),
|
| 70 |
+
port=device_data.get('port'),
|
| 71 |
+
device_type='external'
|
| 72 |
+
)
|
| 73 |
+
self.external_devices.append(device)
|
| 74 |
+
|
| 75 |
+
def _is_duplicate_device(self, device_data) -> bool:
|
| 76 |
+
"""التحقق من عدم تكرار الجهاز"""
|
| 77 |
+
return any(
|
| 78 |
+
d.ip == device_data.get('ip')
|
| 79 |
+
for d in self.local_devices + self.external_devices
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
class LocalListener:
|
| 83 |
+
def __init__(self, discoverer):
|
| 84 |
+
self.discoverer = discoverer
|
| 85 |
+
|
| 86 |
+
def add_service(self, zc, type_, name):
|
| 87 |
+
self._process_service(zc, type_, name)
|
| 88 |
+
|
| 89 |
+
def update_service(self, zc, type_, name):
|
| 90 |
+
self._process_service(zc, type_, name)
|
| 91 |
+
|
| 92 |
+
def _process_service(self, zc, type_, name):
|
| 93 |
+
info = zc.get_service_info(type_, name)
|
| 94 |
+
if info and info.addresses:
|
| 95 |
+
ip = socket.inet_ntoa(info.addresses[0])
|
| 96 |
+
device = Device(
|
| 97 |
+
name=name,
|
| 98 |
+
ip=ip,
|
| 99 |
+
port=info.port,
|
| 100 |
+
device_type='local'
|
| 101 |
+
)
|
| 102 |
+
if not self.discoverer._is_duplicate_device(device.__dict__):
|
| 103 |
+
self.discoverer.local_devices.append(device)
|
| 104 |
+
|
| 105 |
+
def main():
|
| 106 |
+
discoverer = NetworkDiscoverer()
|
| 107 |
+
|
| 108 |
+
while True:
|
| 109 |
+
try:
|
| 110 |
+
devices = discoverer.discover_all_devices()
|
| 111 |
+
|
| 112 |
+
print("\n" + "="*40)
|
| 113 |
+
print(f"الأجهزة المحلية ({len(devices['local'])}):") # تم تصحيح الأقواس هنا
|
| 114 |
+
for device in devices['local']:
|
| 115 |
+
print(f"- {device.name} ({device.ip}:{device.port})")
|
| 116 |
+
|
| 117 |
+
print(f"\nالأجهزة الخارجية ({len(devices['external'])}):") # و هنا
|
| 118 |
+
for device in devices['external']:
|
| 119 |
+
print(f"- {device.name} ({device.ip})")
|
| 120 |
+
|
| 121 |
+
time.sleep(10)
|
| 122 |
+
except KeyboardInterrupt:
|
| 123 |
+
print("\nتم إيقاف الاكتشاف.")
|
| 124 |
+
break
|
| 125 |
+
|
| 126 |
+
if __name__ == "__main__":
|
| 127 |
+
main()
|
peer_registry.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# peer_registry.py
|
| 2 |
+
import socket
|
| 3 |
+
import time
|
| 4 |
+
from zeroconf import Zeroconf, ServiceBrowser
|
| 5 |
+
|
| 6 |
+
class Listener:
|
| 7 |
+
def __init__(self):
|
| 8 |
+
self.peers = []
|
| 9 |
+
|
| 10 |
+
def add_service(self, zc, type, name):
|
| 11 |
+
info = zc.get_service_info(type, name)
|
| 12 |
+
if info:
|
| 13 |
+
ip = socket.inet_ntoa(info.addresses[0])
|
| 14 |
+
port = info.port
|
| 15 |
+
self.peers.append(f"{ip}:{port}")
|
| 16 |
+
|
| 17 |
+
def discover_peers(timeout=2):
|
| 18 |
+
zc = Zeroconf()
|
| 19 |
+
listener = Listener()
|
| 20 |
+
ServiceBrowser(zc, "_http._tcp.local.", listener)
|
| 21 |
+
time.sleep(timeout)
|
| 22 |
+
zc.close()
|
| 23 |
+
return listener.peers
|
| 24 |
+
|
| 25 |
+
# استخدام Zeroconf لاكتشاف الأقران
|
| 26 |
+
zeroconf = Zeroconf()
|
| 27 |
+
listener = Listener()
|
| 28 |
+
browser = ServiceBrowser(zeroconf, "_taskdist._tcp.local.", listener)
|
| 29 |
+
time.sleep(2) # انتظار لاكتشاف الأقران
|
| 30 |
+
available_peers = listener.peers # ["192.168.1.2:7520", "10.0.0.5:7520"]
|
| 31 |
+
|
processor_manager.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# processor_manager.py
|
| 2 |
+
import psutil
|
| 3 |
+
from collections import deque
|
| 4 |
+
|
| 5 |
+
cpu_load = psutil.cpu_percent(interval=1) # نسبة استخدام CPU خلال ثانية
|
| 6 |
+
mem_available = psutil.virtual_memory().available / (1024**2) # الذاكرة المتاحة بالميجابايت
|
| 7 |
+
|
| 8 |
+
if cpu_load > 70 or mem_available < 500: # إذا تجاوز CPU 50% أو الذاكرة أقل من 500MB
|
| 9 |
+
trigger_offload() # تشغيل عملية التوزيع
|
| 10 |
+
|
| 11 |
+
class ResourceMonitor:
|
| 12 |
+
def __init__(self):
|
| 13 |
+
self.cpu_history = deque(maxlen=10)
|
| 14 |
+
self.mem_history = deque(maxlen=10)
|
| 15 |
+
|
| 16 |
+
def current_load(self):
|
| 17 |
+
# قياس الحمل الحالي
|
| 18 |
+
cpu = psutil.cpu_percent(interval=0.5) / 100.0
|
| 19 |
+
mem = psutil.virtual_memory().available / (1024**2)
|
| 20 |
+
|
| 21 |
+
# حفظ في التاريخ
|
| 22 |
+
self.cpu_history.append(cpu)
|
| 23 |
+
self.mem_history.append(mem)
|
| 24 |
+
|
| 25 |
+
# حساب المتوسطات
|
| 26 |
+
avg_cpu = sum(self.cpu_history) / len(self.cpu_history)
|
| 27 |
+
avg_mem = sum(self.mem_history) / len(self.mem_history)
|
| 28 |
+
|
| 29 |
+
return {
|
| 30 |
+
"instant": {"cpu": cpu, "mem": mem},
|
| 31 |
+
"average": {"cpu": avg_cpu, "mem": avg_mem},
|
| 32 |
+
"recommendation": "offload" if avg_cpu > 0.7 or avg_mem < 500 else "local"
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
def should_offload(task_complexity=0):
|
| 36 |
+
monitor = ResourceMonitor()
|
| 37 |
+
status = monitor.current_load()
|
| 38 |
+
|
| 39 |
+
# قرار التوزيع يعتمد على:
|
| 40 |
+
if (
|
| 41 |
+
status['average']['cpu'] > 0.6 or
|
| 42 |
+
status['average']['mem'] < 500 or
|
| 43 |
+
task_complexity > 75
|
| 44 |
+
):
|
| 45 |
+
return True
|
| 46 |
+
return False
|
| 47 |
+
|
remote_executor.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# remote_executor.py
|
| 2 |
+
import requests
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
# استخدم عنوان السيرفر الخارجي من المتغير البيئي أو قيمة افتراضية
|
| 6 |
+
REMOTE_SERVER = os.getenv("REMOTE_SERVER", "http://89.111.171.92:7520/run")
|
| 7 |
+
|
| 8 |
+
def execute_remotely(func_name, args=[], kwargs={}):
|
| 9 |
+
try:
|
| 10 |
+
payload = {
|
| 11 |
+
"func": func_name,
|
| 12 |
+
"args": args,
|
| 13 |
+
"kwargs": kwargs
|
| 14 |
+
}
|
| 15 |
+
response = requests.post(REMOTE_SERVER, json=payload, timeout=10)
|
| 16 |
+
response.raise_for_status()
|
| 17 |
+
data = response.json()
|
| 18 |
+
return data.get("result", "⚠️ لا يوجد نتيجة")
|
| 19 |
+
except Exception as e:
|
| 20 |
+
return f"❌ فشل التنفيذ البعيد: {str(e)}"
|
| 21 |
+
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
flask
|
| 2 |
+
requests
|
| 3 |
+
psutil
|
| 4 |
+
zeroconf
|
| 5 |
+
flask_cors
|
rpc_server.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# rpc_server.py
|
| 2 |
+
|
| 3 |
+
from flask import Flask, request, jsonify
|
| 4 |
+
import smart_tasks # ✅ غيّرنا من your_tasks إلى الاسم الحقيقي لملف المهام
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
# إعداد تسجيل الأحداث في ملف
|
| 8 |
+
logging.basicConfig(
|
| 9 |
+
filename="server.log",
|
| 10 |
+
level=logging.INFO,
|
| 11 |
+
format="%(asctime)s - %(levelname)s - %(message)s"
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
app = Flask(__name__)
|
| 15 |
+
|
| 16 |
+
@app.route("/health")
|
| 17 |
+
def health():
|
| 18 |
+
return jsonify(status="ok")
|
| 19 |
+
|
| 20 |
+
@app.route("/run", methods=["POST"])
|
| 21 |
+
def run():
|
| 22 |
+
try:
|
| 23 |
+
data = request.get_json()
|
| 24 |
+
func_name = data.get("func")
|
| 25 |
+
args = data.get("args", [])
|
| 26 |
+
kwargs = data.get("kwargs", {})
|
| 27 |
+
|
| 28 |
+
fn = getattr(smart_tasks, func_name, None)
|
| 29 |
+
if not fn:
|
| 30 |
+
logging.warning(f"❌ لم يتم العثور على الدالة: {func_name}")
|
| 31 |
+
return jsonify(error="Function not found"), 404
|
| 32 |
+
|
| 33 |
+
logging.info(f"⚙️ تنفيذ الدالة: {func_name} من جهاز آخر")
|
| 34 |
+
result = fn(*args, **kwargs)
|
| 35 |
+
return jsonify(result=result)
|
| 36 |
+
|
| 37 |
+
except Exception as e:
|
| 38 |
+
logging.error(f"🔥 خطأ أثناء تنفيذ المهمة: {str(e)}")
|
| 39 |
+
return jsonify(error=str(e)), 500
|
| 40 |
+
|
| 41 |
+
if __name__ == "__main__":
|
| 42 |
+
# ✅ تأكد أن هذا المنفذ 7520 أو اللي خصصته مفتوح في الجدار الناري
|
| 43 |
+
app.run(host="0.0.0.0", port=7520)
|
| 44 |
+
|
security_layer.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# security_layer.py
|
| 2 |
+
from cryptography.hazmat.primitives import hashes, serialization
|
| 3 |
+
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
| 4 |
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
| 5 |
+
from cryptography.fernet import Fernet
|
| 6 |
+
import os
|
| 7 |
+
import base64
|
| 8 |
+
|
| 9 |
+
class SecurityManager:
|
| 10 |
+
def __init__(self, shared_secret: str):
|
| 11 |
+
self._key = self._derive_key(shared_secret)
|
| 12 |
+
self._cipher = Fernet(self._key)
|
| 13 |
+
self._private_key = rsa.generate_private_key(
|
| 14 |
+
public_exponent=65537,
|
| 15 |
+
key_size=2048
|
| 16 |
+
)
|
| 17 |
+
self._peer_keys = {}
|
| 18 |
+
|
| 19 |
+
def encrypt_data(self, data: bytes) -> bytes:
|
| 20 |
+
"""تشفير البيانات بالمفتاح المتماثل"""
|
| 21 |
+
return self._cipher.encrypt(data)
|
| 22 |
+
|
| 23 |
+
def decrypt_data(self, encrypted: bytes) -> bytes:
|
| 24 |
+
"""فك تشفير البيانات"""
|
| 25 |
+
return self._cipher.decrypt(encrypted)
|
| 26 |
+
|
| 27 |
+
def sign_task(self, task: Dict) -> Dict:
|
| 28 |
+
"""توقيع المهمة رقميًا"""
|
| 29 |
+
signature = self._private_key.sign(
|
| 30 |
+
json.dumps(task).encode(),
|
| 31 |
+
padding.PSS(
|
| 32 |
+
mgf=padding.MGF1(hashes.SHA256()),
|
| 33 |
+
salt_length=padding.PSS.MAX_LENGTH
|
| 34 |
+
),
|
| 35 |
+
hashes.SHA256()
|
| 36 |
+
)
|
| 37 |
+
return {**task, '_signature': base64.b64encode(signature).decode()}
|
| 38 |
+
|
| 39 |
+
def verify_task(self, signed_task: Dict) -> bool:
|
| 40 |
+
"""التحقق من صحة التوقيع"""
|
| 41 |
+
if '_signature' not in signed_task:
|
| 42 |
+
return False
|
| 43 |
+
|
| 44 |
+
signature = base64.b64decode(signed_task['_signature'])
|
| 45 |
+
task_copy = {k: v for k, v in signed_task.items() if k != '_signature'}
|
| 46 |
+
|
| 47 |
+
try:
|
| 48 |
+
self._peer_keys[signed_task['sender_id']].verify(
|
| 49 |
+
signature,
|
| 50 |
+
json.dumps(task_copy).encode(),
|
| 51 |
+
padding.PSS(
|
| 52 |
+
mgf=padding.MGF1(hashes.SHA256()),
|
| 53 |
+
salt_length=padding.PSS.MAX_LENGTH
|
| 54 |
+
),
|
| 55 |
+
hashes.SHA256()
|
| 56 |
+
)
|
| 57 |
+
return True
|
| 58 |
+
except:
|
| 59 |
+
return False
|
| 60 |
+
|
| 61 |
+
def _derive_key(self, password: str) -> bytes:
|
| 62 |
+
"""اشتقاق مفتاح تشفير من كلمة المرور"""
|
| 63 |
+
salt = b'salt_placeholder' # يجب تغييره في الإنتاج
|
| 64 |
+
kdf = PBKDF2HMAC(
|
| 65 |
+
algorithm=hashes.SHA256(),
|
| 66 |
+
length=32,
|
| 67 |
+
salt=salt,
|
| 68 |
+
iterations=100000
|
| 69 |
+
)
|
| 70 |
+
return base64.urlsafe_b64encode(kdf.derive(password.encode()))
|
server.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, request, jsonify
|
| 2 |
+
from flask_cors import CORS
|
| 3 |
+
from your_tasks import multiply_task # دالة مربوطة بـ @offload
|
| 4 |
+
|
| 5 |
+
app = Flask(__name__)
|
| 6 |
+
CORS(app)
|
| 7 |
+
|
| 8 |
+
@app.route('/multiply', methods=['POST'])
|
| 9 |
+
def multiply():
|
| 10 |
+
try:
|
| 11 |
+
data = request.get_json()
|
| 12 |
+
a = data.get("a", 0)
|
| 13 |
+
b = data.get("b", 0)
|
| 14 |
+
result_dict = multiply_task(a, b) # دالة offload
|
| 15 |
+
return jsonify({"result": result_dict["result"]})
|
| 16 |
+
except Exception as e:
|
| 17 |
+
return jsonify({"error": str(e)}), 500
|
| 18 |
+
|
| 19 |
+
if __name__ == "__main__":
|
| 20 |
+
# هذا العنوان يسمح بالاستماع على IP خارجي لتلقي الاتصالات من الإنترنت
|
| 21 |
+
app.run(host="0.0.0.0", port=7520)
|
| 22 |
+
|
| 23 |
+
|
setup.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# setup.py
|
| 2 |
+
from setuptools import setup, find_packages
|
| 3 |
+
|
| 4 |
+
setup(
|
| 5 |
+
name="distributed-task-system",
|
| 6 |
+
version="1.0",
|
| 7 |
+
packages=find_packages(),
|
| 8 |
+
install_requires=[
|
| 9 |
+
"flask",
|
| 10 |
+
"requests",
|
| 11 |
+
"psutil",
|
| 12 |
+
"zeroconf",
|
| 13 |
+
"flask_cors",
|
| 14 |
+
"numpy"
|
| 15 |
+
],
|
| 16 |
+
entry_points={
|
| 17 |
+
"console_scripts": [
|
| 18 |
+
"dts-start=dts_cli:main",
|
| 19 |
+
],
|
| 20 |
+
},
|
| 21 |
+
data_files=[
|
| 22 |
+
("/etc/systemd/system", ["dts.service"])
|
| 23 |
+
]
|
| 24 |
+
)
|
setup_fonts.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
|
| 4 |
+
FONT_DIR = "fonts"
|
| 5 |
+
FONT_PATH = os.path.join(FONT_DIR, "Cairo-Regular.ttf")
|
| 6 |
+
|
| 7 |
+
# 🔥 رابط مباشر من Google Fonts CDN - Cairo Regular وزن 400
|
| 8 |
+
URL = "https://fonts.cdnfonts.com/s/12667/Cairo-Regular.woff"
|
| 9 |
+
|
| 10 |
+
os.makedirs(FONT_DIR, exist_ok=True)
|
| 11 |
+
|
| 12 |
+
print("[+] تحميل الخط Cairo-Regular...")
|
| 13 |
+
response = requests.get(URL)
|
| 14 |
+
if response.status_code == 200:
|
| 15 |
+
with open(FONT_PATH, "wb") as f:
|
| 16 |
+
f.write(response.content)
|
| 17 |
+
print(f"[✅] تم حفظ الخط في {FONT_PATH}")
|
| 18 |
+
else:
|
| 19 |
+
print(f"[❌] فشل تحميل الخط! كود الحالة: {response.status_code}")
|
| 20 |
+
|
startup.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from autostart_config import AutoStartManager
|
| 2 |
+
from distributed_executor import DistributedExecutor
|
| 3 |
+
import logging
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
def main():
|
| 7 |
+
# إعداد السجل
|
| 8 |
+
logging.basicConfig(
|
| 9 |
+
filename='autostart.log',
|
| 10 |
+
level=logging.INFO,
|
| 11 |
+
format='%(asctime)s - %(levelname)s - %(message)s'
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
# تحميل الإعدادات
|
| 16 |
+
autostart = AutoStartManager()
|
| 17 |
+
if not autostart.config['enabled']:
|
| 18 |
+
logging.info("التشغيل التلقائي معطل في الإعدادات")
|
| 19 |
+
return
|
| 20 |
+
|
| 21 |
+
# بدء النظام الرئيسي
|
| 22 |
+
logging.info("بدء تشغيل النظام الموزع تلقائياً")
|
| 23 |
+
executor = DistributedExecutor("my_shared_secret_123")
|
| 24 |
+
executor.peer_registry.register_service("auto_node", 7520)
|
| 25 |
+
|
| 26 |
+
# هنا يمكنك إضافة أي مهام تريد تشغيلها تلقائياً
|
| 27 |
+
# executor.submit(...)
|
| 28 |
+
|
| 29 |
+
# البقاء نشطاً
|
| 30 |
+
while True:
|
| 31 |
+
time.sleep(60)
|
| 32 |
+
|
| 33 |
+
except Exception as e:
|
| 34 |
+
logging.error(f"خطأ في التشغيل التلقائي: {str(e)}")
|
| 35 |
+
sys.exit(1)
|
| 36 |
+
|
| 37 |
+
if __name__ == "__main__":
|
| 38 |
+
main()
|
task_interceptor.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from processor_manager import should_offload
|
| 2 |
+
from remote_executor import execute_remotely
|
| 3 |
+
|
| 4 |
+
def offload_if_needed(func):
|
| 5 |
+
def wrapper(*args, **kwargs):
|
| 6 |
+
if should_offload():
|
| 7 |
+
return execute_remotely(func.__name__, args, kwargs)
|
| 8 |
+
return func(*args, **kwargs)
|
| 9 |
+
return wrapper
|
task_splitter.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# task_splitter.py
|
| 2 |
+
from typing import Dict, Any, List
|
| 3 |
+
import networkx as nx
|
| 4 |
+
import hashlib
|
| 5 |
+
|
| 6 |
+
class TaskSplitter:
|
| 7 |
+
def __init__(self):
|
| 8 |
+
self._dependency_graph = nx.DiGraph()
|
| 9 |
+
|
| 10 |
+
def add_task(self, task_id: str, task: Dict[str, Any], deps: List[str] = []):
|
| 11 |
+
"""إضافة مهمة مع تبعياتها"""
|
| 12 |
+
self._dependency_graph.add_node(task_id, task=task)
|
| 13 |
+
for dep in deps:
|
| 14 |
+
self._dependency_graph.add_edge(dep, task_id)
|
| 15 |
+
|
| 16 |
+
def split_tasks(self) -> Dict[str, List[Dict]]:
|
| 17 |
+
"""تقسيم المهام إلى مجموعات متوازية"""
|
| 18 |
+
clusters = {}
|
| 19 |
+
for component in nx.weakly_connected_components(self._dependency_graph):
|
| 20 |
+
subgraph = self._dependency_graph.subgraph(component)
|
| 21 |
+
for level, nodes in enumerate(nx.topological_generations(subgraph)):
|
| 22 |
+
for node in nodes:
|
| 23 |
+
cluster_id = self._generate_cluster_id(node, level)
|
| 24 |
+
if cluster_id not in clusters:
|
| 25 |
+
clusters[cluster_id] = []
|
| 26 |
+
clusters[cluster_id].append({
|
| 27 |
+
'task_id': node,
|
| 28 |
+
'task': self._dependency_graph.nodes[node]['task']
|
| 29 |
+
})
|
| 30 |
+
return clusters
|
| 31 |
+
|
| 32 |
+
def _generate_cluster_id(self, node: str, level: int) -> str:
|
| 33 |
+
"""إنشاء معرف فريد لكل مجموعة مهام"""
|
| 34 |
+
deps = list(self._dependency_graph.predecessors(node))
|
| 35 |
+
deps_hash = hashlib.md5(','.join(sorted(deps)).encode()).hexdigest()[:8]
|
| 36 |
+
return f"L{level}-{deps_hash}"
|
your_tasks.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from offload_lib import offload
|
| 2 |
+
import time
|
| 3 |
+
import math
|
| 4 |
+
import numpy as np
|
| 5 |
+
from typing import Dict, List, Union
|
| 6 |
+
|
| 7 |
+
# ============= وظائف مساعدة =============
|
| 8 |
+
def _validate_positive_integer(value: int, name: str) -> None:
|
| 9 |
+
"""تحقق من أن القيمة عدد صحيح موجب."""
|
| 10 |
+
if not isinstance(value, int) or value <= 0:
|
| 11 |
+
raise ValueError(f"{name} يجب أن يكون عددًا صحيحًا موجبًا")
|
| 12 |
+
|
| 13 |
+
# ============= المهام الرئيسية =============
|
| 14 |
+
@offload
|
| 15 |
+
def matrix_multiply(size: int) -> Dict[str, List[List[float]]]:
|
| 16 |
+
"""
|
| 17 |
+
ضرب مصفوفات كبير (عملية كثيفة الحساب)
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
size: حجم المصفوفة (size x size)
|
| 21 |
+
|
| 22 |
+
Returns:
|
| 23 |
+
نتيجة الضرب كقائمة ثنائية الأبعاد
|
| 24 |
+
"""
|
| 25 |
+
_validate_positive_integer(size, "حجم المصفوفة")
|
| 26 |
+
|
| 27 |
+
A = np.random.rand(size, size)
|
| 28 |
+
B = np.random.rand(size, size)
|
| 29 |
+
return {"result": np.dot(A, B).tolist()}
|
| 30 |
+
|
| 31 |
+
@offload
|
| 32 |
+
def prime_calculation(n: int) -> Dict[str, Union[int, List[int]]]:
|
| 33 |
+
"""
|
| 34 |
+
حساب الأعداد الأولية حتى n
|
| 35 |
+
|
| 36 |
+
Args:
|
| 37 |
+
n: الحد الأعلى للبحث عن الأعداد الأولية
|
| 38 |
+
|
| 39 |
+
Returns:
|
| 40 |
+
قامة بالأعداد الأولية وعددها
|
| 41 |
+
"""
|
| 42 |
+
_validate_positive_integer(n, "الحد الأعلى للأعداد الأولية")
|
| 43 |
+
|
| 44 |
+
primes = []
|
| 45 |
+
for num in range(2, n + 1):
|
| 46 |
+
is_prime = True
|
| 47 |
+
for i in range(2, int(math.sqrt(num)) + 1):
|
| 48 |
+
if num % i == 0:
|
| 49 |
+
is_prime = False
|
| 50 |
+
break
|
| 51 |
+
if is_prime:
|
| 52 |
+
primes.append(num)
|
| 53 |
+
return {"primes_count": len(primes), "primes": primes}
|
| 54 |
+
|
| 55 |
+
@offload
|
| 56 |
+
def data_processing(data_size: int) -> Dict[str, int]:
|
| 57 |
+
"""
|
| 58 |
+
محاكاة معالجة بيانات كبيرة
|
| 59 |
+
|
| 60 |
+
Args:
|
| 61 |
+
data_size: عدد عناصر البيانات المراد معالجتها
|
| 62 |
+
|
| 63 |
+
Returns:
|
| 64 |
+
عدد العناصر المعالجة
|
| 65 |
+
"""
|
| 66 |
+
_validate_positive_integer(data_size, "حجم البيانات")
|
| 67 |
+
|
| 68 |
+
processed_data = []
|
| 69 |
+
for i in range(data_size):
|
| 70 |
+
result = sum(math.sin(x) * math.cos(x) for x in range(i, i+100))
|
| 71 |
+
processed_data.append(result)
|
| 72 |
+
return {"processed_items": len(processed_data)}
|
| 73 |
+
|
| 74 |
+
@offload
|
| 75 |
+
def image_processing_emulation(iterations: int) -> Dict[str, Union[int, float]]:
|
| 76 |
+
"""
|
| 77 |
+
محاكاة معالجة الصور (عملية كثيفة الحساب)
|
| 78 |
+
|
| 79 |
+
Args:
|
| 80 |
+
iterations: عدد التكرارات للمحاكاة
|
| 81 |
+
|
| 82 |
+
Returns:
|
| 83 |
+
نتائج المحاكاة
|
| 84 |
+
"""
|
| 85 |
+
_validate_positive_integer(iterations, "عدد التكرارات")
|
| 86 |
+
|
| 87 |
+
results = []
|
| 88 |
+
for i in range(iterations):
|
| 89 |
+
value = sum(math.exp(math.sin(x)) for x in range(i, i+50))
|
| 90 |
+
results.append(value)
|
| 91 |
+
return {"iterations": iterations, "result": sum(results)}
|
| 92 |
+
|
| 93 |
+
# ============= اختبار الوظائف محلياً =============
|
| 94 |
+
if __name__ == "__main__":
|
| 95 |
+
# اختبار جميع الوظائف مع معالجة الأخطاء
|
| 96 |
+
test_cases = [
|
| 97 |
+
("matrix_multiply", matrix_multiply, 100),
|
| 98 |
+
("prime_calculation", prime_calculation, 1000),
|
| 99 |
+
("data_processing", data_processing, 500),
|
| 100 |
+
("image_processing", image_processing_emulation, 50)
|
| 101 |
+
]
|
| 102 |
+
|
| 103 |
+
for name, func, arg in test_cases:
|
| 104 |
+
try:
|
| 105 |
+
print(f"\nجارِ تشغيل: {name}...")
|
| 106 |
+
start = time.time()
|
| 107 |
+
result = func(arg)
|
| 108 |
+
duration = time.time() - start
|
| 109 |
+
|
| 110 |
+
print(f"النتيجة: {str(result)[:200]}...")
|
| 111 |
+
print(f"الوقت المستغرق: {duration:.2f} ثانية")
|
| 112 |
+
except Exception as e:
|
| 113 |
+
print(f"فشل {name}: {str(e)}")
|