pyflow-net 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
PyFlow/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from . import flow_setup
2
+
3
+ def main():
4
+ flow_setup.main()
PyFlow/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from . import main
2
+
3
+ if __name__=="__main__":
4
+ main()
@@ -0,0 +1,252 @@
1
+ import os
2
+ import ast
3
+ import json
4
+ import copy
5
+ import shlex
6
+ import shutil
7
+ import traceback
8
+ import threading
9
+ import subprocess
10
+ from datetime import datetime
11
+ from .network_api import connect_tcp
12
+
13
+ server_instance = None
14
+ client_instance = None
15
+ command_counter = {}
16
+ command_counter_lock = threading.Lock()
17
+
18
+
19
+ def _load_json_file(path):
20
+ try:
21
+ with open(path, "r", encoding="utf-8") as f:
22
+ data = json.load(f)
23
+ return data if isinstance(data, dict) else {}
24
+ except Exception:
25
+ return {}
26
+
27
+
28
+ def _merge_log_dicts(base_data, extra_data):
29
+ merged = {}
30
+ for command, entries in base_data.items():
31
+ merged[command] = list(entries) if isinstance(entries, list) else [entries]
32
+ for command, entries in extra_data.items():
33
+ if command not in merged:
34
+ merged[command] = []
35
+ if isinstance(entries, list):
36
+ merged[command].extend(entries)
37
+ else:
38
+ merged[command].append(entries)
39
+ return merged
40
+
41
+
42
+ def _merge_all_logs(log_dir, merged_filename="merged_logs.json"):
43
+ merged_data = {}
44
+ for filename in sorted(os.listdir(log_dir)):
45
+ if not filename.endswith(".json") or filename == merged_filename:
46
+ continue
47
+ file_path = os.path.join(log_dir, filename)
48
+ if os.path.isfile(file_path):
49
+ merged_data = _merge_log_dicts(merged_data, _load_json_file(file_path))
50
+ with open(os.path.join(log_dir, merged_filename), "w", encoding="utf-8") as f:
51
+ json.dump(merged_data, f, ensure_ascii=False, indent=2)
52
+
53
+
54
+ def _setup_command():
55
+ print("Setting up server command...")
56
+ server_instance.register_command(
57
+ command_name="/command", handler=_command_handler, where_to_run="client", run_in_thread=True
58
+ )
59
+ server_instance.register_command(
60
+ command_name="/command_done",
61
+ handler=_command_done_dealing_server,
62
+ where_to_run="server",
63
+ run_in_thread=True,
64
+ )
65
+
66
+
67
+ def _setup_client_command():
68
+ print("Setting up client command...")
69
+ client_instance.register_command(
70
+ command_name="/command",
71
+ handler=_command_handler_server_setup,
72
+ where_to_run="server",
73
+ run_in_thread=True,
74
+ )
75
+
76
+
77
+ def _command_handler(sock, addr, cmd):
78
+ print(f"Received command from {addr}: {cmd}")
79
+ client_class = server_instance.clients
80
+ cmd_part = shlex.split(cmd)
81
+ del cmd_part[0] # Remove the command name
82
+ command_client_pair = []
83
+ clients_list = []
84
+ commands_list = []
85
+ clients_num = 0
86
+ for part in cmd_part:
87
+ if part.startswith("(") and part.endswith(")"):
88
+ try:
89
+ clients_num += 1
90
+ client_part = ast.literal_eval(part)
91
+ clients_list.append(client_part)
92
+ print(f"client part: {clients_list}")
93
+ except Exception as e:
94
+ print(f"Error evaluating part '{part}': {e}")
95
+ else:
96
+ if clients_num != 0:
97
+ command_client_pair.append([commands_list, clients_list])
98
+ clients_num = 0
99
+ clients_list = []
100
+ commands_list = []
101
+ commands_list.append(part)
102
+ print(f"command part: {commands_list}")
103
+ if clients_num != 0:
104
+ print([commands_list, clients_list])
105
+ command_client_pair.append([commands_list, clients_list])
106
+ clients_num = 0
107
+ clients_list = []
108
+ commands_list = []
109
+ client_id = 0
110
+ for pair in command_client_pair:
111
+ for msg in pair[0]:
112
+ command_msg = (
113
+ "/command" + " " + shlex.quote(msg) + " " + shlex.quote(str(len(pair[0]))) + " "
114
+ )
115
+ for client in pair[1]:
116
+ temp_msg = (
117
+ command_msg
118
+ + shlex.quote(str(client_id))
119
+ + " "
120
+ + shlex.quote(str(client))
121
+ + "\n"
122
+ )
123
+ client_socket = client_class[client]["socket"]
124
+ server_instance.send_message(client_socket=client_socket, message=temp_msg)
125
+ print(f"Sending command to clients: {command_msg}")
126
+ client_id += 1
127
+
128
+
129
+ def _command_handler_server_setup(sock, addr, cmd):
130
+ global command_counter
131
+ print(f"Received command from {addr}: {cmd}")
132
+ try:
133
+ cmd_parts = shlex.split(cmd)
134
+ except Exception as e:
135
+ print(f"Error parsing command: {e}")
136
+ return
137
+ if len(cmd_parts) < 3 or cmd_parts[0] != "/command":
138
+ print("Invalid command format.")
139
+ return
140
+ command = cmd_parts[1]
141
+ client_addr = cmd_parts[4]
142
+ command_total_num = int(cmd_parts[2])
143
+ client_id = cmd_parts[3]
144
+ log_dir = os.path.join(os.path.dirname(__file__), "logs")
145
+ if not os.path.exists(log_dir):
146
+ os.makedirs(log_dir)
147
+ with command_counter_lock:
148
+ if str(client_id) not in command_counter:
149
+ command_counter[str(client_id)] = 1
150
+ else:
151
+ command_counter[str(client_id)] += 1
152
+ cmd_id = copy.copy(command_counter[str(client_id)])
153
+ log_filename = "logs{}.json".format("_" + str(client_id))
154
+ log_path = os.path.join(log_dir, log_filename)
155
+ try:
156
+ result = subprocess.run(command, capture_output=True, text=True, shell=True)
157
+ output = result.stdout.strip()
158
+ error = result.stderr.strip()
159
+ returncode = result.returncode
160
+ except Exception as e:
161
+ output = ""
162
+ error = str(e)
163
+ returncode = -1
164
+ log_line = {
165
+ "timestamp": datetime.now().isoformat(),
166
+ "command": command,
167
+ "cmd_id": str(cmd_id),
168
+ "client_id": str(client_id),
169
+ "client": client_addr,
170
+ "from": str(addr),
171
+ "output": output,
172
+ "error": error,
173
+ "returncode": returncode,
174
+ }
175
+ if os.path.exists(log_path):
176
+ with open(log_path, "r", encoding="utf-8") as f:
177
+ try:
178
+ log_data = json.load(f)
179
+ except Exception:
180
+ log_data = {}
181
+ else:
182
+ log_data = {}
183
+ if command not in log_data:
184
+ log_data[command] = []
185
+ log_data[command].append(log_line)
186
+ with open(log_path, "w", encoding="utf-8") as f:
187
+ json.dump(log_data, f, ensure_ascii=False, indent=2)
188
+ print(f"Log written to {log_path}")
189
+ msg = '/file "{}"'.format(log_path)
190
+ if cmd_id == command_total_num:
191
+ command_counter[str(client_id)] = 0
192
+ client_instance.file_transfer_client_recv_client_start(
193
+ message=msg, file_folder_abspath=None
194
+ )
195
+ client_instance.send_message(
196
+ client_socket=client_instance.client_socket,
197
+ message='/command_done "{}" "{}"'.format(log_filename, log_path),
198
+ )
199
+ else:
200
+ pass
201
+ print("Dealing the command successfully!")
202
+
203
+
204
+ def _command_done_dealing_server(sock, addr, cmd):
205
+ cmd_parts = shlex.split(cmd)
206
+ log_filename = cmd_parts[1]
207
+ log_path = cmd_parts[2]
208
+ log_dir = os.path.join(os.path.dirname(__file__), "logs")
209
+ os.makedirs(log_dir, exist_ok=True)
210
+ received_log_file = os.path.join(server_instance.file_transfer_dir, os.path.basename(log_path))
211
+ destination_log_file = os.path.join(log_dir, log_filename)
212
+ try:
213
+ if os.path.exists(destination_log_file):
214
+ existing_logs = _load_json_file(destination_log_file)
215
+ incoming_logs = _load_json_file(received_log_file)
216
+ merged_logs = _merge_log_dicts(existing_logs, incoming_logs)
217
+ with open(destination_log_file, "w", encoding="utf-8") as f:
218
+ json.dump(merged_logs, f, ensure_ascii=False, indent=2)
219
+ os.remove(received_log_file)
220
+ else:
221
+ shutil.move(received_log_file, destination_log_file)
222
+ _merge_all_logs(log_dir)
223
+ print("Command Done!")
224
+ except Exception:
225
+ traceback.print_exc()
226
+ print("ErrorWhileMovingTheLogFile: moving log file failed.")
227
+
228
+
229
+ def client_setup():
230
+ global client_instance
231
+ client_instance = connect_tcp.TCP_Client_Base(
232
+ host="127.0.0.1",
233
+ port=65000,
234
+ client_host="127.0.0.1",
235
+ is_input_command_in_console=True,
236
+ is_extend_command=True,
237
+ )
238
+ _setup_client_command()
239
+ client_instance.start_TCP_client()
240
+
241
+
242
+ def server_setup():
243
+ global server_instance
244
+ server_instance = connect_tcp.TCP_Server_Base(
245
+ host="127.0.0.1",
246
+ port=65000,
247
+ max_clients=10,
248
+ is_input_command_in_console=True,
249
+ is_extend_command=True,
250
+ )
251
+ _setup_command()
252
+ server_instance.start_TCP_Server()
PyFlow/flow_setup.py ADDED
@@ -0,0 +1,283 @@
1
+ import os
2
+ import sys
3
+ import json
4
+ import shutil
5
+ import platform
6
+ import tempfile
7
+ import argparse
8
+ import subprocess
9
+ import traceback
10
+ from .network_api.connect_tcp import TCP_Server_Base, TCP_Client_Base
11
+
12
+ SERVER_DEFAULTS = {
13
+ "host": "127.0.0.1",
14
+ "port": 65432,
15
+ "max_clients": 10,
16
+ "port_add_step": 1,
17
+ "port_range_num": 100,
18
+ "max_file_transfer_thread_num": 10,
19
+ "is_hand_alloc_port": False,
20
+ "is_input_command_in_console": True,
21
+ "max_custom_workers": 10,
22
+ "is_extend_command": False,
23
+ "is_enable_encrypto": True,
24
+ "is_custom_keys": None,
25
+ }
26
+
27
+ CLIENT_DEFAULTS = {
28
+ "host": None,
29
+ "client_host": "127.0.0.1",
30
+ "port": 65432,
31
+ "client_port": None,
32
+ "timeout": None,
33
+ "port_add_step": 1,
34
+ "max_thread_num": 10,
35
+ "is_input_command_in_console": True,
36
+ "is_wait_server": True,
37
+ "max_custom_workers": 10,
38
+ "is_extend_command": False,
39
+ "is_enable_encrypto": True,
40
+ "is_custom_keys": None,
41
+ }
42
+
43
+
44
+ def parse_addr_port(addr_port):
45
+ host, port_str = addr_port.strip().split(":")
46
+ return host, int(port_str)
47
+
48
+
49
+ def load_existing_config():
50
+ if os.path.exists("setup.json"):
51
+ with open("setup.json", "r", encoding="utf-8") as f:
52
+ return json.load(f)
53
+ return {"servers": [], "clients": []}
54
+
55
+
56
+ def complete_server_config(cfg):
57
+ full = SERVER_DEFAULTS.copy()
58
+ full.update(cfg)
59
+ return full
60
+
61
+
62
+ def complete_client_config(cfg):
63
+ full = CLIENT_DEFAULTS.copy()
64
+ full.update(cfg)
65
+ return full
66
+
67
+
68
+ def save_config(servers, clients):
69
+ if servers:
70
+ servers = [servers[-1]]
71
+ else:
72
+ servers = []
73
+ if clients:
74
+ clients = [clients[-1]]
75
+ else:
76
+ clients = []
77
+ data = {
78
+ "servers": [complete_server_config(cfg) for cfg in servers],
79
+ "clients": [complete_client_config(cfg) for cfg in clients],
80
+ }
81
+ with open("setup.json", "w", encoding="utf-8") as f:
82
+ json.dump(data, f, indent=4, ensure_ascii=False)
83
+
84
+
85
+ def launch_instance(config, instance_type):
86
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False, encoding="utf-8") as f:
87
+ json.dump(config, f)
88
+ config_file_path = f.name
89
+ script = os.path.abspath(__file__)
90
+ python = sys.executable
91
+ launch_arg = f"--launch_{instance_type}"
92
+ system = platform.system()
93
+ try:
94
+ if system == "Windows":
95
+ cmd = f'start cmd /k {python} {script} {launch_arg} --config-file "{config_file_path}"'
96
+ subprocess.Popen(cmd, shell=True)
97
+ elif system == "Linux":
98
+ terminals = ["gnome-terminal", "xterm", "x-terminal-emulator"]
99
+ launched = False
100
+ for term in terminals:
101
+ if shutil.which(term):
102
+ cmd = f'{term} -- {python} {script} {launch_arg} --config-file "{config_file_path}"'
103
+ subprocess.Popen(cmd, shell=True)
104
+ launched = True
105
+ break
106
+ if not launched:
107
+ subprocess.Popen(
108
+ [python, script, launch_arg, "--config-file", config_file_path],
109
+ stdout=subprocess.DEVNULL,
110
+ stderr=subprocess.DEVNULL,
111
+ stdin=subprocess.DEVNULL,
112
+ start_new_session=True,
113
+ )
114
+ elif system == "Darwin":
115
+ cmd = f'open -a Terminal.app {python} {script} {launch_arg} --config-file "{config_file_path}"'
116
+ subprocess.Popen(cmd, shell=True)
117
+ else:
118
+ subprocess.Popen(
119
+ [python, script, launch_arg, "--config-file", config_file_path],
120
+ stdout=subprocess.DEVNULL,
121
+ stderr=subprocess.DEVNULL,
122
+ stdin=subprocess.DEVNULL,
123
+ start_new_session=True,
124
+ )
125
+ except Exception as e:
126
+ print(f"Failed to launch instance: {e}")
127
+ traceback.print_exc()
128
+ try:
129
+ os.unlink(config_file_path)
130
+ except:
131
+ pass
132
+
133
+
134
+ def interactive_collect():
135
+ servers = []
136
+ clients = []
137
+ while True:
138
+ print("\n--- Add New Instance ---")
139
+ while True:
140
+ type_choice = input("Select type (0=Server, 1=Client): ").strip()
141
+ if type_choice in ("0", "1"):
142
+ break
143
+ print("Invalid input, please enter 0 or 1")
144
+ is_server = type_choice == "0"
145
+ while True:
146
+ setup_addr = input("Enter bind address and port (format host:port): ").strip()
147
+ try:
148
+ host, port = parse_addr_port(setup_addr)
149
+ break
150
+ except:
151
+ print("Invalid format, please retry")
152
+ if is_server:
153
+ config = {"host": host, "port": port}
154
+ servers = [config]
155
+ print(f"Server config set to: {host}:{port}")
156
+ else:
157
+ while True:
158
+ conn_addr = input(
159
+ "Enter server address and port to connect (format host:port): "
160
+ ).strip()
161
+ try:
162
+ srv_host, srv_port = parse_addr_port(conn_addr)
163
+ break
164
+ except:
165
+ print("Invalid format, please retry")
166
+ config = {"client_host": host, "client_port": port, "host": srv_host, "port": srv_port}
167
+ clients = [config]
168
+ print(f"Client config set to: local {host}:{port} -> server {srv_host}:{srv_port}")
169
+ cont = input("Continue adding more instances? (Y/N): ").strip().lower()
170
+ if cont != "y":
171
+ break
172
+ return servers, clients
173
+
174
+
175
+ def generate_configs_from_args(args):
176
+ if args.setup_num > 1:
177
+ print("Warning: --setup_num is ignored because only one instance per type is allowed.")
178
+ host, port = parse_addr_port(args.setup_addr_port)
179
+ if args.type == 0:
180
+ config = {"host": host, "port": port}
181
+ return [config], []
182
+ else:
183
+ srv_host, srv_port = parse_addr_port(args.connect_addr_port)
184
+ config = {"client_host": host, "client_port": port, "host": srv_host, "port": srv_port}
185
+ return [], [config]
186
+
187
+
188
+ def run_launched_instance(instance_type, config_file_path):
189
+ try:
190
+ with open(config_file_path, "r", encoding="utf-8") as f:
191
+ config = json.load(f)
192
+ try:
193
+ os.unlink(config_file_path)
194
+ except:
195
+ pass
196
+ if instance_type == "server":
197
+ server = TCP_Server_Base(**config)
198
+ else:
199
+ client = TCP_Client_Base(**config)
200
+ except Exception as e:
201
+ print(f"Failed to start instance: {e}")
202
+ traceback.print_exc()
203
+ input("Press any key to exit...")
204
+ sys.exit(1)
205
+
206
+
207
+ def main():
208
+ if "--launch_server" in sys.argv:
209
+ idx = sys.argv.index("--launch_server")
210
+ try:
211
+ cfg_idx = sys.argv.index("--config-file", idx)
212
+ if cfg_idx + 1 < len(sys.argv):
213
+ config_file = sys.argv[cfg_idx + 1]
214
+ run_launched_instance("server", config_file)
215
+ else:
216
+ print("Error: missing --config-file argument")
217
+ sys.exit(1)
218
+ except ValueError:
219
+ print("Error: missing --config-file argument")
220
+ sys.exit(1)
221
+ return
222
+ if "--launch_client" in sys.argv:
223
+ idx = sys.argv.index("--launch_client")
224
+ try:
225
+ cfg_idx = sys.argv.index("--config-file", idx)
226
+ if cfg_idx + 1 < len(sys.argv):
227
+ config_file = sys.argv[cfg_idx + 1]
228
+ run_launched_instance("client", config_file)
229
+ else:
230
+ print("Error: missing --config-file argument")
231
+ sys.exit(1)
232
+ except ValueError:
233
+ print("Error: missing --config-file argument")
234
+ sys.exit(1)
235
+ return
236
+ parser = argparse.ArgumentParser(description="Flow Setup Launcher")
237
+ parser.add_argument("--type", type=int, choices=[0, 1], help="0=Server, 1=Client")
238
+ parser.add_argument("--setup_addr_port", type=str, help="Bind address and port (host:port)")
239
+ parser.add_argument(
240
+ "--connect_addr_port", type=str, help="Server address and port to connect (client required)"
241
+ )
242
+ parser.add_argument(
243
+ "--setup_num", type=int, default=1, help="Number of instances to launch (only 1 is allowed)"
244
+ )
245
+ args = parser.parse_args()
246
+ if args.type is not None:
247
+ if args.type == 0 and args.connect_addr_port is not None:
248
+ print("Error: --connect_addr_port cannot be used in Server mode")
249
+ sys.exit(1)
250
+ if args.type == 1 and (args.setup_addr_port is None or args.connect_addr_port is None):
251
+ print("Error: Client mode requires both --setup_addr_port and --connect_addr_port")
252
+ sys.exit(1)
253
+ if args.type == 0 and args.setup_addr_port is None:
254
+ print("Error: Server mode requires --setup_addr_port")
255
+ sys.exit(1)
256
+ servers, clients = generate_configs_from_args(args)
257
+ save_config(servers, clients)
258
+ for cfg in servers:
259
+ launch_instance(cfg, "server")
260
+ for cfg in clients:
261
+ launch_instance(cfg, "client")
262
+ return
263
+ if os.path.exists("setup.json"):
264
+ choice = input("setup.json exists. Overwrite configuration data? (Y/N): ").strip().lower()
265
+ if choice == "n":
266
+ config_data = load_existing_config()
267
+ for cfg in config_data.get("servers", []):
268
+ launch_instance(cfg, "server")
269
+ for cfg in config_data.get("clients", []):
270
+ launch_instance(cfg, "client")
271
+ return
272
+ else:
273
+ choice = "y"
274
+ servers, clients = interactive_collect()
275
+ save_config(servers, clients)
276
+ for cfg in servers:
277
+ launch_instance(cfg, "server")
278
+ for cfg in clients:
279
+ launch_instance(cfg, "client")
280
+
281
+
282
+ if __name__ == "__main__":
283
+ main()
File without changes