Added batch script execution function

This commit is contained in:
zhaoyafan 2025-03-01 01:05:40 +08:00
parent 3090a0f68d
commit ed496a08c2
2 changed files with 274 additions and 72 deletions

View File

@ -40,7 +40,7 @@ from selenium.common.exceptions import NoSuchWindowException
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings, QWebEngineProfile
from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor
from PyQt5.QtNetwork import QNetworkProxyFactory
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox, QSystemTrayIcon, QMenu, QAction, QDesktopWidget, QShortcut
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox, QSystemTrayIcon, QMenu, QAction, QDesktopWidget, QShortcut, QFileDialog
from PyQt5.QtCore import Qt, QCoreApplication, QTimer, QUrl
from PyQt5.QtGui import QIcon, QKeySequence
from typing import List
@ -1206,22 +1206,38 @@ class BrowserPluginParent:
def __init__(self):
self.app_id = None
self.thread = None
self.result_color = None
self.result = None
self.run_task_exception = None
self.exception = None
self.user_id = None
self.instant_message = None
self.batch_mode = None
self._rs = 0
self._re = 0
@property
def running_time(self):
return (int(time.time()) - self._rs) if self._rs > self._re else (self._re - self._rs)
def _run_task(self, *args, **kwargs):
try:
self.result = self.running(*args, **kwargs) or ''
self._rs = int(time.time())
res = self.running(*args, **kwargs)
if isinstance(res, (tuple, list)):
self.result_color = res[0]
self.result = res[1]
except Exception as e:
self.run_task_exception = e
self.exception = e
self.message_except(e)
finally:
self._re = int(time.time())
def message(self, message=None):
print('[%s]: %s' % (str(self.app_id), message), file=sys.stderr)
if len(str(message)) <= 4096:
notification_send(app_id=self.app_id, title=self.name, message='%s' % (message,))
if (self.batch_mode is True) == 1:
return None
self.instant_message and self.instant_message(json.dumps({
'time': int(time.time()), 'type': 'message', 'data': {'user_id': str(self.user_id), 'content': str(message), 'title': str(self.app_id)}
}))
@ -1241,7 +1257,7 @@ class BrowserPluginParent:
params = BrowserPluginRunningParam({'driver': driver, 'message': self.message, 'logging': self.logging, 'requirements': requirements})
thread = threading.Thread(target=self._run_task, args=(params,))
self.thread = thread
thread.daemon = False
thread.daemon = True
thread.start()
return thread
@ -1254,7 +1270,7 @@ class BrowserPluginParent:
self.thread = None
def state(self):
return self.thread.is_alive() if self.thread else None
return self.thread.is_alive() if self.thread else False
@staticmethod
def running(param: BrowserPluginRunningParam):
@ -1273,6 +1289,7 @@ class BrowserPluginFileTest(BrowserPluginParent):
@staticmethod
def running(param):
param.message(param.requirements)
return '#FFFFFF', '已完成'
class BrowserPluginMultFileTest(BrowserPluginParent):
@ -1287,6 +1304,7 @@ class BrowserPluginMultFileTest(BrowserPluginParent):
@staticmethod
def running(param):
param.message(param.requirements)
return '#FFFFFF', '已完成'
class BrowserPluginTextTest(BrowserPluginParent):
@ -1301,6 +1319,7 @@ class BrowserPluginTextTest(BrowserPluginParent):
@staticmethod
def running(param):
param.message(param.requirements)
return '#FFFFFF', '已完成'
class BrowserPluginMultTextTest(BrowserPluginParent):
@ -1315,6 +1334,7 @@ class BrowserPluginMultTextTest(BrowserPluginParent):
@staticmethod
def running(param):
param.message(param.requirements)
return '#FFFFFF', '已完成'
class BrowserPluginLoggingTest(BrowserPluginParent):
@ -1329,6 +1349,7 @@ class BrowserPluginLoggingTest(BrowserPluginParent):
@staticmethod
def running(param):
param.logging(param.requirements[0])
return '#FFFFFF', '已完成'
class BrowserPluginMessageTest(BrowserPluginParent):
@ -1343,6 +1364,7 @@ class BrowserPluginMessageTest(BrowserPluginParent):
@staticmethod
def running(param):
param.message(param.requirements[0])
return '#FFFFFF', '已完成'
class BrowserPluginFileCommandDebug(BrowserPluginParent):
@ -1358,6 +1380,7 @@ class BrowserPluginFileCommandDebug(BrowserPluginParent):
def running(param):
file = param.requirements[0][0]['path']
exec(open(file, mode='r', encoding='utf-8').read())
return '#FFFFFF', '已完成'
class BrowserPluginTextCommandDebug(BrowserPluginParent):
@ -1373,6 +1396,7 @@ class BrowserPluginTextCommandDebug(BrowserPluginParent):
def running(param):
code = param.requirements[0]
code and exec(code)
return '#FFFFFF', '已完成'
class BrowserManagerDataStorage(dict):
@ -1455,14 +1479,6 @@ class BrowserManagerUserRunning:
return False
except PermissionError:
return True
# s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# s.settimeout(0.01)
# try:
# return s.connect_ex(('127.0.0.1', self.remote_debugging_port)) == 0
# except socket.timeout:
# return False
# finally:
# s.close()
@func_set_timeout(0.05)
def _get_window_handles(self):
@ -1533,11 +1549,14 @@ class BrowserManagerUserRunning:
'user_data_dir': self.user_data_dir,
'remote_debugging_port': self.remote_debugging_port,
'is_running': self.is_running,
'current_plugin_executing': self.plugin.id if self.plugin and self.plugin.state() else None,
'url': self.url,
'title': self.title,
'alert': self.alert,
'window_handles': self.window_handles
'window_handles': self.window_handles,
'plugin_running': self.plugin.state() if self.plugin else False,
'plugin_running_time': self.plugin.running_time if self.plugin else None,
'plugin_execution_result': self.plugin.result if self.plugin else None,
'plugin_execution_result_color': self.plugin.result_color if self.plugin else None
}
def app_id(self):
@ -1553,6 +1572,46 @@ class BrowserManagerUserRunning:
self.user_name = user_name
class BrowserManagerBusy(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
class BrowserPluginBatchExecution:
def __init__(self):
self.plugin_id = None
self.plugin_name = None
self.plugin_time = None
self.users = None
self.plugins = None
self.results = None
self.running = None
def set_plugin_for_monitor(self, users: list, plugins: list, plugin_id: str, plugin_name: str):
if self.running is True:
raise Exception('Running.')
self.plugin_id = plugin_id
self.plugin_name = plugin_name
self.plugin_time = int(time.time())
self.users = users
self.plugins = plugins
threading.Thread(target=self._monitor_until_all_completed).start()
def _monitor_until_all_completed(self):
if not isinstance(self.plugins, list):
return None
self.results = []
self.running = True
while True:
time.sleep(2.5)
if all(not plugin.state() for plugin in self.plugins):
break
for plugin in self.plugins:
self.results.append([plugin.app_id, plugin.running_time, plugin.result])
self.running = False
class BrowserManager:
def __init__(self, runner, driver: str, binary: str, plugin_result_dir: str, manager_data_file: str, browser_data_home: str, browser_init_home: str, use_selenium_wire: int):
self.runner = runner
@ -1580,6 +1639,7 @@ class BrowserManager:
self.user_in_operation = []
self.plugins_int = {}
self.plugins_ext = {}
self.plugin_batch_execution = BrowserPluginBatchExecution()
def _generate_remote_debugging_port(self):
exist_ports = [value['remote_debugging_port'] for key, value in self.data_storage['browser_user'].items()]
@ -1589,8 +1649,7 @@ class BrowserManager:
for user_id, running in self.user_running.items():
if (not running) == 1:
continue
run = BrowserManagerUserRunning(running)
run.update_status_running()
running.update_status_running()
def _get_user_name(self, user_id: str):
return str(self.data_storage['browser_user'][user_id]['user_name'] or '')
@ -1731,21 +1790,15 @@ class BrowserManager:
def user_operate_starting(self, user_id: str):
user_id = str(user_id)
# with self.threading_lock:
# if (user_id not in self.user_in_operation) == 1:
# self.user_in_operation.append(user_id)
# else:
# raise RuntimeError('Busy.')
if (user_id not in self.user_in_operation) == 1:
self.user_in_operation.append(user_id)
else:
raise RuntimeError('Busy.')
raise BrowserManagerBusy('Busy.')
def user_operate_complete(self, user_id: str):
user_id = str(user_id)
with self.threading_lock:
while user_id in self.user_in_operation:
self.user_in_operation.remove(user_id)
while user_id in self.user_in_operation:
self.user_in_operation.remove(user_id)
def is_user_data_occupied(self, user_id: str):
try:
@ -1783,8 +1836,12 @@ class BrowserManager:
plugin = self.plugins[plugin_id]()
plugin.run(self._get_user_app_id(user_id), user_id, driver, requirements, self.runner.web_server.websocket_connection_manager.send_broadcast_use_sync)
running.set_plugin(plugin)
finally:
self.user_operate_complete(user_id)
except BrowserManagerBusy:
raise
except Exception:
self.user_operate_complete(user_id)
raise
@calculate_execution_time
def plugin_shutdown(self, user_id: str):
@ -1797,8 +1854,45 @@ class BrowserManager:
raise Exception('The plugin is not running.')
plugin = running.plugin
plugin.interrupt()
finally:
self.user_operate_complete(user_id)
except BrowserManagerBusy:
raise
except Exception:
self.user_operate_complete(user_id)
raise
def plugin_all_users_run(self, plugin_id: str, requirements=None):
try:
if (self.plugin_batch_execution.running is True) == 1:
raise Exception('Still in batch execution.')
users = []
plugins = []
for user_id in self.user_ids_online():
try:
self.plugin_run(user_id, plugin_id, requirements)
users.append(user_id)
plugin = BrowserManagerUserRunning(self.user_running[user_id]).plugin
plugin.batch_mode = True
plugins.append(plugin)
time.sleep(0.1)
except Exception:
pass
self.plugin_batch_execution.set_plugin_for_monitor(users, plugins, plugin_id, self.plugins[plugin_id].name)
except Exception:
raise
def plugin_all_users_shutdown(self):
try:
for user_id in self.plugin_batch_execution.users:
try:
self.plugin_shutdown(user_id)
except Exception:
pass
except Exception:
pass
def plugin_list(self):
return {plugin_id: {'name': plugin_class.name, 'requirements': plugin_class.requirements} for plugin_id, plugin_class in self.plugins.items()}
def user_ids(self):
return [user_id for user_id in self.data_storage['browser_user'].keys()]
@ -1808,14 +1902,17 @@ class BrowserManager:
return [user_id for user_id, running in self.user_running.items() if running and running.is_running]
@calculate_execution_time
def user_all_details(self, user_id: str = None):
plugins = {plugin_id: {'name': plugin_class.name, 'requirements': plugin_class.requirements} for plugin_id, plugin_class in self.plugins.items()}
def user_all_details(self):
details = {user_id: self.user_running[user_id].status() for user_id in self.user_ids() if user_id in self.user_running.keys()}
details = {user_id: {**detail, **{'plugins': plugins}} for user_id, detail in details.items()}
if (user_id and user_id in details.keys()) == 1:
return details[user_id]
else:
return details
return {
'user_online_count': len(self.user_ids_online()),
'user_details': details,
'plugin_batch_execution_id': self.plugin_batch_execution.plugin_id,
'plugin_batch_execution_name': self.plugin_batch_execution.plugin_name,
'plugin_batch_execution_time': self.plugin_batch_execution.plugin_time,
'plugin_batch_execution_status': self.plugin_batch_execution.running,
'plugin_batch_execution_result': self.plugin_batch_execution.results
}
@calculate_execution_time
def user_set_name(self, user_id: str, user_name: str):
@ -1829,8 +1926,12 @@ class BrowserManager:
self.data_storage['browser_user'][user_id]['user_name'] = user_name
self.data_storage.save()
self.user_running[user_id].set_user_name(user_name)
finally:
self.user_operate_complete(user_id)
except BrowserManagerBusy:
raise
except Exception:
self.user_operate_complete(user_id)
raise
@func_set_timeout(0.55)
@calculate_execution_time
@ -1871,8 +1972,12 @@ class BrowserManager:
os.path.exists(data_dir) or shutil.copytree(self.browser_init_home, data_dir)
else:
os.path.exists(data_dir) or os.makedirs(data_dir)
finally:
self.user_operate_complete(user_id)
except BrowserManagerBusy:
raise
except Exception:
self.user_operate_complete(user_id)
raise
@calculate_execution_time
def user_del(self, user_id: str):
@ -1889,8 +1994,12 @@ class BrowserManager:
self.data_storage['browser_user'].pop(user_id)
self.data_storage.save()
self.user_running.pop(user_id)
finally:
self.user_operate_complete(user_id)
except BrowserManagerBusy:
raise
except Exception:
self.user_operate_complete(user_id)
raise
@calculate_execution_time
def user_run(self, user_id: str):
@ -1917,8 +2026,12 @@ class BrowserManager:
res_interceptor=self.use_selenium_wire and self.res_interceptor
)
running.set_driver(driver)
finally:
self.user_operate_complete(user_id)
except BrowserManagerBusy:
raise
except Exception:
self.user_operate_complete(user_id)
raise
@calculate_execution_time
def user_die(self, user_id: str):
@ -1944,8 +2057,45 @@ class BrowserManager:
pass
else:
raise Exception('The debug port is not in listening.')
finally:
self.user_operate_complete(user_id)
except BrowserManagerBusy:
raise
except Exception:
self.user_operate_complete(user_id)
raise
class ApplicationSetting(dict):
def __init__(self, file: str):
self._data_file = os.path.abspath(file)
super().__init__(self._get_json_data(self._data_file, {}))
@staticmethod
def _get_json_data(file: str, data=None):
if os.path.exists(file):
try:
return json.loads(open(file=file, mode='r', encoding='utf-8').read())
except json.decoder.JSONDecodeError:
return data
return data
@staticmethod
def _put_json_data(file: str, data=None):
with open(file=file, mode='w', encoding='utf-8') as f:
flock(f, LOCK_EX)
res = f.write(json.dumps(data, indent=4, ensure_ascii=True))
flock(f, LOCK_UN)
return res
def __getitem__(self, item):
if item in self:
return super().__getitem__(item)
else:
return None
def __setitem__(self, key, value):
super().__setitem__(key, value)
self._put_json_data(self._data_file, self)
class MainWindow(QMainWindow):
@ -1968,10 +2118,11 @@ class MainWindow(QMainWindow):
self.setWindowFlags(self.windowFlags() | Qt.WindowMinMaxButtonsHint)
screen = QDesktopWidget().screenGeometry()
self.screen_w, self.screen_h = screen.width(), screen.height()
self.window_w, self.window_h = round(480 * self.scale_rate), round(868 * self.scale_rate)
self.window_w, self.window_h = round(455 * self.scale_rate), round(855 * self.scale_rate)
self.window_x, self.window_y = 0, 0
self.setFixedSize(self.window_w, self.window_h)
QNetworkProxyFactory.setUseSystemConfiguration(False)
self.setting = ApplicationSetting(os.path.join(runner.app_data, 'application.json'))
self.webview = WebEngineView()
self.webview_cache_dir = os.path.join(tempfile.gettempdir(), hashlib.md5(bytes('%s_cache' % (__file__,), encoding='utf-8')).hexdigest()[:32])
web_settings = self.webview.settings()
@ -1996,8 +2147,8 @@ class MainWindow(QMainWindow):
['调试:打印环境变量信息', self.on_debug_print_environment],
['调试:打印模块搜索路径', self.on_debug_print_module_path],
['调试:重新加载外部插件', self.on_debug_reload_external_plugins],
['配置首选文件', self.on_config_preferred_file],
['重载面板', self.webview.reload],
['显示窗口', self.window_show],
['退出', self.exit]
]
for action_item in action_list:
@ -2028,7 +2179,8 @@ class MainWindow(QMainWindow):
window_x=self.pos().x(),
window_y=self.pos().y()
)
self.webview.load(QUrl('http://%s:%s/' % ('127.0.0.1' if self.web_listen_host == '0.0.0.0' else self.web_listen_host, self.web_listen_port)))
self.webview.load(QUrl('http://%s:%s/index.html' % ('127.0.0.1' if self.web_listen_host == '0.0.0.0' else self.web_listen_host, self.web_listen_port)))
self.webview.reload()
def exit(self):
self.webview.deleteLater()
@ -2053,6 +2205,14 @@ class MainWindow(QMainWindow):
def window_position_reset(self):
self.setGeometry(self.window_x, self.window_y + (self.frameGeometry().height() - self.geometry().height()), self.window_w, self.window_h)
def on_config_preferred_file(self):
self.window_show()
filename, _ = QFileDialog.getOpenFileName(self, '选择文件', '/', 'All Files (*)')
if (filename and os.path.exists(filename)) == 1:
filename = os.path.abspath(filename)
self.setting['plugin_preferred_file'] = filename
QMessageBox.information(self, '提示', 'The preferred file has changed to %s' % (filename,))
def on_tray_icon_update(self):
self.tray_icon.setToolTip('活跃用户:%s/%s' % (len(self.runner.web_server.browser_manager.user_ids_online()), len(self.runner.web_server.browser_manager.user_ids())))
@ -2169,6 +2329,14 @@ class WebServer:
pass
await self.websocket_connection_manager.connect(websocket, 1)
@self.app.api_route(methods=['GET', 'POST'], path='/plugin_preferred_file')
def api_plugin_preferred_file():
file = self.runner.window and self.runner.window.setting['plugin_preferred_file']
if (file and os.path.exists(file)) == 1:
return JSONResponse(status_code=200, content=self.message(0, '', file))
else:
return JSONResponse(status_code=200, content=self.message(0, '', None))
@self.app.api_route(methods=['POST'], path='/plugin_run')
def api_plugin_run(data: WebServerAPIJSONData):
try:
@ -2185,6 +2353,26 @@ class WebServer:
except Exception as e:
return self.message(1, '%s' % (e,))
@self.app.api_route(methods=['POST'], path='/plugin_all_users_run')
def api_plugin_all_users_run(data: WebServerAPIJSONData):
try:
self.browser_manager.plugin_all_users_run(data.data['plugin_id'], data.data['requirements'])
return self.message(0)
except Exception as e:
return self.message(1, '%s' % (e,))
@self.app.api_route(methods=['POST'], path='/plugin_all_users_shutdown')
def api_plugin_all_users_shutdown():
try:
self.browser_manager.plugin_all_users_shutdown()
return self.message(0)
except Exception as e:
return self.message(1, '%s' % (e,))
@self.app.api_route(methods=['GET', 'POST'], path='/plugin_list')
def api_plugin_list():
return self.browser_manager.plugin_list()
@self.app.api_route(methods=['POST'], path='/upload_files')
def api_upload_files(files: List[UploadFile] = File(...)):
if (len(files) > 25) == 1:
@ -2197,7 +2385,7 @@ class WebServer:
file_save_path = os.path.join(self.upload_dir, '%s_%s' % (int(time.time() * 1000), file.filename))
with open(file_save_path, 'wb') as f:
f.write(file.file.read())
uploaded_files.append({'filename': file.filename, 'size': file.size, 'type': file.content_type, 'path': file_save_path})
uploaded_files.append(file_save_path)
return JSONResponse(status_code=200, content=self.message(0, '', uploaded_files))
@self.app.api_route(methods=['GET', 'POST'], path='/user_all_details')
@ -2265,7 +2453,7 @@ class WebServer:
@self.app.api_route(methods=['GET'], path='/')
def index():
return self.statics(os.path.join(self.www, 'index.html'))
raise HTTPException(status_code=403)
@self.app.api_route(methods=['GET'], path='/200')
def state():
@ -2435,6 +2623,7 @@ class MainRunner:
sys.path.append(os.path.join(os.path.dirname(__file__), 'site-packages.zip'))
sys.path.append(os.path.join(self.app_data, 'Packages'))
sys.path.append(os.path.join(self.app_data, 'site-packages.zip'))
print('Startup.', file=sys.stderr)
try:
self.preprocessing()
except Exception:

File diff suppressed because one or more lines are too long