diff --git a/Galactic.py b/Galactic.py
index 920c226..09a0848 100644
--- a/Galactic.py
+++ b/Galactic.py
@@ -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:
diff --git a/www/index.html b/www/index.html
index a18f5a1..fc1c08a 100644
--- a/www/index.html
+++ b/www/index.html
@@ -1,6 +1,5 @@
-
@@ -16,34 +15,47 @@
-
-
+
-
-
+
+
用户管理
+
+
+
+
+
{{ pluginBatchExecutionStatus ? '正在执行:' + pluginBatchExecutionName : '批量执行' }}
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
未命名
-
{{ item.user_name }}
-
+
+
+
未命名
+
{{ item.user_name }}
+
{{ item.plugin_execution_result }}
-
×
+
×
-
+
{{ userLoggingList[item.user_id].data }}
-
未运行
+
用户未运行
{{ item.alert || item.title }}
{{ !item.window_handles ? '0个页面' : item.window_handles.length + '个页面' }}
@@ -51,27 +63,28 @@
-
-
-
+
+
+
-
-
-
+
+
+
+
+
-
+
+
暂时还没有用户,请先添加~
+
-
暂时还没有用户,请先添加~
-
-
+
-