ipc-framework 1.0.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.
- ipc_framework/__init__.py +28 -0
- ipc_framework/client.py +274 -0
- ipc_framework/core.py +353 -0
- ipc_framework/demo.py +268 -0
- ipc_framework/examples/__init__.py +13 -0
- ipc_framework/examples/basic_server.py +209 -0
- ipc_framework/examples/chat_client.py +168 -0
- ipc_framework/examples/file_client.py +211 -0
- ipc_framework/examples/monitoring_client.py +252 -0
- ipc_framework/exceptions.py +43 -0
- ipc_framework/py.typed +2 -0
- ipc_framework/server.py +298 -0
- ipc_framework-1.0.0.dist-info/METADATA +512 -0
- ipc_framework-1.0.0.dist-info/RECORD +18 -0
- ipc_framework-1.0.0.dist-info/WHEEL +5 -0
- ipc_framework-1.0.0.dist-info/entry_points.txt +6 -0
- ipc_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
- ipc_framework-1.0.0.dist-info/top_level.txt +1 -0
ipc_framework/demo.py
ADDED
@@ -0,0 +1,268 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
IPC Framework Demo
|
4
|
+
|
5
|
+
This script demonstrates all the key features of the IPC framework:
|
6
|
+
- Multiple applications and channels
|
7
|
+
- Different message types (request/response, publish/subscribe, notifications)
|
8
|
+
- Connection management
|
9
|
+
- Error handling
|
10
|
+
|
11
|
+
Run this script to see the framework in action!
|
12
|
+
"""
|
13
|
+
|
14
|
+
import time
|
15
|
+
import threading
|
16
|
+
import random
|
17
|
+
from . import FrameworkServer, FrameworkClient, MessageType, Message
|
18
|
+
|
19
|
+
|
20
|
+
def run_demo():
|
21
|
+
print("š IPC Framework Demo")
|
22
|
+
print("=" * 50)
|
23
|
+
|
24
|
+
# Step 1: Create and start server
|
25
|
+
print("\nš” Starting IPC Server...")
|
26
|
+
server = FrameworkServer(host="localhost", port=8888)
|
27
|
+
|
28
|
+
# Create multiple applications
|
29
|
+
chat_app = server.create_application("chat_app", "Chat Application")
|
30
|
+
file_app = server.create_application("file_share", "File Sharing")
|
31
|
+
monitor_app = server.create_application("monitoring", "System Monitor")
|
32
|
+
|
33
|
+
# Create channels in each application
|
34
|
+
chat_app.create_channel("general")
|
35
|
+
chat_app.create_channel("announcements")
|
36
|
+
|
37
|
+
file_app.create_channel("upload")
|
38
|
+
file_app.create_channel("download")
|
39
|
+
|
40
|
+
monitor_app.create_channel("metrics")
|
41
|
+
monitor_app.create_channel("alerts")
|
42
|
+
|
43
|
+
# Set up message handlers
|
44
|
+
def chat_handler(message: Message):
|
45
|
+
username = message.payload.get('username', 'Anonymous')
|
46
|
+
text = message.payload.get('text', '')
|
47
|
+
print(f"š¬ [Chat] {username}: {text}")
|
48
|
+
|
49
|
+
def file_handler(message: Message):
|
50
|
+
action = message.payload.get('action', 'unknown')
|
51
|
+
filename = message.payload.get('filename', 'unknown')
|
52
|
+
print(f"š [File] {action}: {filename}")
|
53
|
+
|
54
|
+
def metric_handler(message: Message):
|
55
|
+
metric = message.payload.get('metric', 'unknown')
|
56
|
+
value = message.payload.get('value', 0)
|
57
|
+
print(f"š [Metrics] {metric}: {value}%")
|
58
|
+
|
59
|
+
# Generate alert if CPU high
|
60
|
+
if metric == 'cpu_usage' and value > 80:
|
61
|
+
alert_msg = Message(
|
62
|
+
message_id="",
|
63
|
+
app_id="monitoring",
|
64
|
+
channel_id="alerts",
|
65
|
+
message_type=MessageType.NOTIFICATION,
|
66
|
+
payload={
|
67
|
+
'type': 'high_cpu_alert',
|
68
|
+
'metric': metric,
|
69
|
+
'value': value,
|
70
|
+
'severity': 'warning'
|
71
|
+
},
|
72
|
+
timestamp=time.time()
|
73
|
+
)
|
74
|
+
server._handle_publish(alert_msg, monitor_app.get_channel("alerts"))
|
75
|
+
|
76
|
+
# Set handlers
|
77
|
+
chat_app.get_channel("general").set_handler(MessageType.REQUEST, chat_handler)
|
78
|
+
file_app.get_channel("upload").set_handler(MessageType.REQUEST, file_handler)
|
79
|
+
monitor_app.get_channel("metrics").set_handler(MessageType.NOTIFICATION, metric_handler)
|
80
|
+
|
81
|
+
# Start server
|
82
|
+
server.start()
|
83
|
+
print("ā
Server started successfully!")
|
84
|
+
|
85
|
+
# Show server status
|
86
|
+
print(f"\nš Server Configuration:")
|
87
|
+
for app_id, app_info in server.list_applications().items():
|
88
|
+
print(f" š± {app_id} ({app_info['name']})")
|
89
|
+
for channel in app_info['channels']:
|
90
|
+
print(f" šŗ {channel}")
|
91
|
+
|
92
|
+
time.sleep(1)
|
93
|
+
|
94
|
+
# Step 2: Create clients and demonstrate features
|
95
|
+
print("\nš„ Creating Clients...")
|
96
|
+
|
97
|
+
clients = []
|
98
|
+
|
99
|
+
# Chat clients
|
100
|
+
alice = FrameworkClient("chat_app", host="localhost", port=8888)
|
101
|
+
bob = FrameworkClient("chat_app", host="localhost", port=8888)
|
102
|
+
|
103
|
+
# File client
|
104
|
+
file_client = FrameworkClient("file_share", host="localhost", port=8888)
|
105
|
+
|
106
|
+
# Monitoring client
|
107
|
+
monitor_client = FrameworkClient("monitoring", host="localhost", port=8888)
|
108
|
+
|
109
|
+
# Connect all clients
|
110
|
+
for name, client in [("Alice", alice), ("Bob", bob), ("FileClient", file_client), ("Monitor", monitor_client)]:
|
111
|
+
if client.connect():
|
112
|
+
clients.append((name, client))
|
113
|
+
print(f"ā
{name} connected")
|
114
|
+
else:
|
115
|
+
print(f"ā {name} failed to connect")
|
116
|
+
|
117
|
+
time.sleep(1)
|
118
|
+
|
119
|
+
# Step 3: Demonstrate different message patterns
|
120
|
+
print("\nš Demonstrating Message Patterns...")
|
121
|
+
|
122
|
+
# 1. Chat messages (Request pattern)
|
123
|
+
print("\nš¬ Chat Messages:")
|
124
|
+
alice.request("general", {"username": "Alice", "text": "Hello everyone!"})
|
125
|
+
time.sleep(0.5)
|
126
|
+
bob.request("general", {"username": "Bob", "text": "Hi Alice! How are you?"})
|
127
|
+
time.sleep(0.5)
|
128
|
+
alice.request("general", {"username": "Alice", "text": "I'm great! This IPC framework is awesome!"})
|
129
|
+
time.sleep(1)
|
130
|
+
|
131
|
+
# 2. File operations
|
132
|
+
print("\nš File Operations:")
|
133
|
+
file_client.request("upload", {"action": "upload", "filename": "document.pdf", "size": 1024000})
|
134
|
+
time.sleep(0.5)
|
135
|
+
file_client.request("upload", {"action": "upload", "filename": "image.jpg", "size": 2048000})
|
136
|
+
time.sleep(1)
|
137
|
+
|
138
|
+
# 3. Monitoring with alerts
|
139
|
+
print("\nš System Monitoring:")
|
140
|
+
|
141
|
+
# Subscribe to alerts
|
142
|
+
def alert_handler(message: Message):
|
143
|
+
alert_type = message.payload.get('type', 'unknown')
|
144
|
+
metric = message.payload.get('metric', 'unknown')
|
145
|
+
value = message.payload.get('value', 0)
|
146
|
+
print(f"šØ ALERT: {alert_type} - {metric} at {value}%")
|
147
|
+
|
148
|
+
monitor_client.subscribe("alerts", alert_handler)
|
149
|
+
time.sleep(0.5) # Give subscription time to register
|
150
|
+
|
151
|
+
# Send normal metrics
|
152
|
+
monitor_client.notify("metrics", {"metric": "cpu_usage", "value": 45})
|
153
|
+
time.sleep(0.5)
|
154
|
+
monitor_client.notify("metrics", {"metric": "memory_usage", "value": 62})
|
155
|
+
time.sleep(0.5)
|
156
|
+
|
157
|
+
# Send high CPU to trigger alert
|
158
|
+
monitor_client.notify("metrics", {"metric": "cpu_usage", "value": 85})
|
159
|
+
time.sleep(1)
|
160
|
+
|
161
|
+
# 4. Publish/Subscribe pattern
|
162
|
+
print("\nš¢ Publish/Subscribe Demo:")
|
163
|
+
|
164
|
+
# Subscribe to announcements
|
165
|
+
def announcement_handler(message: Message):
|
166
|
+
text = message.payload.get('text', '')
|
167
|
+
print(f"š¢ ANNOUNCEMENT: {text}")
|
168
|
+
|
169
|
+
alice.subscribe("announcements", announcement_handler)
|
170
|
+
bob.subscribe("announcements", announcement_handler)
|
171
|
+
time.sleep(0.5)
|
172
|
+
|
173
|
+
# Publish announcement
|
174
|
+
alice.publish("announcements", {"text": "Server maintenance scheduled for tonight at 11 PM"})
|
175
|
+
time.sleep(1)
|
176
|
+
|
177
|
+
# Step 4: Show server statistics
|
178
|
+
print("\nš Server Statistics:")
|
179
|
+
stats = server.get_stats()
|
180
|
+
print(f" š Active connections: {stats['total_connections']}")
|
181
|
+
print(f" š± Applications: {stats['total_applications']}")
|
182
|
+
|
183
|
+
for app_id, app_info in server.list_applications().items():
|
184
|
+
channels_info = server.list_channels(app_id)
|
185
|
+
print(f" š± {app_id}: {app_info['connections']} connections")
|
186
|
+
for channel_id, channel_info in channels_info.items():
|
187
|
+
print(f" šŗ {channel_id}: {channel_info['subscribers']} subscribers")
|
188
|
+
|
189
|
+
# Step 5: Demonstrate connection management
|
190
|
+
print("\nš Connection Management Demo:")
|
191
|
+
print("Disconnecting some clients...")
|
192
|
+
|
193
|
+
alice.disconnect()
|
194
|
+
print("ā Alice disconnected")
|
195
|
+
time.sleep(0.5)
|
196
|
+
|
197
|
+
file_client.disconnect()
|
198
|
+
print("ā FileClient disconnected")
|
199
|
+
time.sleep(1)
|
200
|
+
|
201
|
+
# Show updated stats
|
202
|
+
stats = server.get_stats()
|
203
|
+
print(f"š Updated connections: {stats['total_connections']}")
|
204
|
+
|
205
|
+
# Step 6: Error handling demo
|
206
|
+
print("\nā ļø Error Handling Demo:")
|
207
|
+
|
208
|
+
# Try to send message with disconnected client
|
209
|
+
try:
|
210
|
+
alice.request("general", {"text": "This should fail"})
|
211
|
+
except Exception as e:
|
212
|
+
print(f"š« Expected error from disconnected client: {type(e).__name__}")
|
213
|
+
|
214
|
+
# Try invalid channel
|
215
|
+
try:
|
216
|
+
bob.request("nonexistent_channel", {"text": "This will work but route to nowhere"})
|
217
|
+
print("š Message sent to auto-created channel (framework auto-creates channels)")
|
218
|
+
except Exception as e:
|
219
|
+
print(f"ā Unexpected error: {e}")
|
220
|
+
|
221
|
+
time.sleep(1)
|
222
|
+
|
223
|
+
# Step 7: Cleanup
|
224
|
+
print("\nš§¹ Cleaning up...")
|
225
|
+
|
226
|
+
# Disconnect remaining clients
|
227
|
+
for name, client in clients:
|
228
|
+
if client.connected:
|
229
|
+
client.disconnect()
|
230
|
+
print(f"ā {name} disconnected")
|
231
|
+
|
232
|
+
# Stop server
|
233
|
+
server.stop()
|
234
|
+
print("š Server stopped")
|
235
|
+
|
236
|
+
print("\nš Demo completed!")
|
237
|
+
print("\nš” Key Features Demonstrated:")
|
238
|
+
print(" ā
Multiple applications with channels (app_id -> channel_id)")
|
239
|
+
print(" ā
Request/Response pattern")
|
240
|
+
print(" ā
Publish/Subscribe pattern")
|
241
|
+
print(" ā
Notifications and alerts")
|
242
|
+
print(" ā
Connection management")
|
243
|
+
print(" ā
Automatic channel creation")
|
244
|
+
print(" ā
Thread-safe operations")
|
245
|
+
print(" ā
Error handling")
|
246
|
+
print(" ā
Server statistics and monitoring")
|
247
|
+
|
248
|
+
print("\nš Try running the example scripts next:")
|
249
|
+
print(" ipc-server # Start the server")
|
250
|
+
print(" ipc-chat YourName # Chat client")
|
251
|
+
print(" ipc-file # File sharing client")
|
252
|
+
print(" ipc-monitor sim # Monitoring simulation")
|
253
|
+
|
254
|
+
|
255
|
+
def main():
|
256
|
+
"""Entry point for the demo command"""
|
257
|
+
try:
|
258
|
+
run_demo()
|
259
|
+
except KeyboardInterrupt:
|
260
|
+
print("\n\nā¹ļø Demo interrupted by user")
|
261
|
+
except Exception as e:
|
262
|
+
print(f"\nš„ Demo error: {e}")
|
263
|
+
import traceback
|
264
|
+
traceback.print_exc()
|
265
|
+
|
266
|
+
|
267
|
+
if __name__ == "__main__":
|
268
|
+
main()
|
@@ -0,0 +1,209 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Basic IPC Server Example
|
4
|
+
|
5
|
+
This example demonstrates how to create an IPC server with multiple applications
|
6
|
+
and channels, showing the hierarchical structure of app_id -> channel_id.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import time
|
10
|
+
import threading
|
11
|
+
from .. import FrameworkServer, MessageType, Message
|
12
|
+
|
13
|
+
|
14
|
+
def setup_chat_application(server: FrameworkServer):
|
15
|
+
"""Setup a chat application with multiple channels"""
|
16
|
+
app = server.create_application("chat_app", "Chat Application")
|
17
|
+
|
18
|
+
# Create channels
|
19
|
+
general_channel = app.create_channel("general")
|
20
|
+
tech_channel = app.create_channel("tech_talk")
|
21
|
+
random_channel = app.create_channel("random")
|
22
|
+
|
23
|
+
# Set up message handlers
|
24
|
+
def handle_chat_message(message: Message):
|
25
|
+
print(f"[{message.channel_id}] Chat message from {message.payload.get('username', 'Anonymous')}: {message.payload.get('text', '')}")
|
26
|
+
|
27
|
+
# Broadcast to all subscribers in this channel
|
28
|
+
broadcast_msg = Message(
|
29
|
+
message_id="",
|
30
|
+
app_id=message.app_id,
|
31
|
+
channel_id=message.channel_id,
|
32
|
+
message_type=MessageType.NOTIFICATION,
|
33
|
+
payload={
|
34
|
+
'type': 'chat_message',
|
35
|
+
'username': message.payload.get('username', 'Anonymous'),
|
36
|
+
'text': message.payload.get('text', ''),
|
37
|
+
'timestamp': message.timestamp
|
38
|
+
},
|
39
|
+
timestamp=time.time()
|
40
|
+
)
|
41
|
+
server._handle_publish(broadcast_msg, app.get_channel(message.channel_id))
|
42
|
+
|
43
|
+
def handle_user_join(message: Message):
|
44
|
+
username = message.payload.get('username', 'Anonymous')
|
45
|
+
print(f"[{message.channel_id}] User {username} joined")
|
46
|
+
|
47
|
+
# Notify other users
|
48
|
+
join_msg = Message(
|
49
|
+
message_id="",
|
50
|
+
app_id=message.app_id,
|
51
|
+
channel_id=message.channel_id,
|
52
|
+
message_type=MessageType.NOTIFICATION,
|
53
|
+
payload={
|
54
|
+
'type': 'user_joined',
|
55
|
+
'username': username,
|
56
|
+
'timestamp': time.time()
|
57
|
+
},
|
58
|
+
timestamp=time.time()
|
59
|
+
)
|
60
|
+
server._handle_publish(join_msg, app.get_channel(message.channel_id))
|
61
|
+
|
62
|
+
# Set handlers for all channels
|
63
|
+
for channel_id in ["general", "tech_talk", "random"]:
|
64
|
+
channel = app.get_channel(channel_id)
|
65
|
+
channel.set_handler(MessageType.REQUEST, handle_chat_message)
|
66
|
+
channel.set_handler(MessageType.NOTIFICATION, handle_user_join)
|
67
|
+
|
68
|
+
|
69
|
+
def setup_file_sharing_application(server: FrameworkServer):
|
70
|
+
"""Setup a file sharing application"""
|
71
|
+
app = server.create_application("file_share", "File Sharing Service")
|
72
|
+
|
73
|
+
# Create channels for different file operations
|
74
|
+
upload_channel = app.create_channel("upload")
|
75
|
+
download_channel = app.create_channel("download")
|
76
|
+
status_channel = app.create_channel("status")
|
77
|
+
|
78
|
+
def handle_file_upload(message: Message):
|
79
|
+
filename = message.payload.get('filename', 'unknown')
|
80
|
+
size = message.payload.get('size', 0)
|
81
|
+
print(f"[file_share:upload] File upload request: {filename} ({size} bytes)")
|
82
|
+
|
83
|
+
# Simulate file processing
|
84
|
+
status_msg = Message(
|
85
|
+
message_id="",
|
86
|
+
app_id=message.app_id,
|
87
|
+
channel_id="status",
|
88
|
+
message_type=MessageType.NOTIFICATION,
|
89
|
+
payload={
|
90
|
+
'type': 'upload_progress',
|
91
|
+
'filename': filename,
|
92
|
+
'progress': 100,
|
93
|
+
'status': 'completed'
|
94
|
+
},
|
95
|
+
timestamp=time.time()
|
96
|
+
)
|
97
|
+
server._handle_publish(status_msg, app.get_channel("status"))
|
98
|
+
|
99
|
+
def handle_file_download(message: Message):
|
100
|
+
filename = message.payload.get('filename', 'unknown')
|
101
|
+
print(f"[file_share:download] File download request: {filename}")
|
102
|
+
|
103
|
+
# Send response back to requester
|
104
|
+
response = Message(
|
105
|
+
message_id="",
|
106
|
+
app_id=message.app_id,
|
107
|
+
channel_id=message.channel_id,
|
108
|
+
message_type=MessageType.RESPONSE,
|
109
|
+
payload={
|
110
|
+
'filename': filename,
|
111
|
+
'download_url': f"http://example.com/files/{filename}",
|
112
|
+
'expires_at': time.time() + 3600 # 1 hour
|
113
|
+
},
|
114
|
+
timestamp=time.time(),
|
115
|
+
reply_to=message.message_id
|
116
|
+
)
|
117
|
+
|
118
|
+
# Find the connection that sent the request and send response
|
119
|
+
connection_id = message.payload.get('connection_id')
|
120
|
+
if connection_id:
|
121
|
+
connection = server.connection_manager.get_connection(connection_id)
|
122
|
+
if connection:
|
123
|
+
server.send_to_connection(connection, response)
|
124
|
+
|
125
|
+
upload_channel.set_handler(MessageType.REQUEST, handle_file_upload)
|
126
|
+
download_channel.set_handler(MessageType.REQUEST, handle_file_download)
|
127
|
+
|
128
|
+
|
129
|
+
def setup_monitoring_application(server: FrameworkServer):
|
130
|
+
"""Setup a monitoring application"""
|
131
|
+
app = server.create_application("monitoring", "System Monitoring")
|
132
|
+
|
133
|
+
metrics_channel = app.create_channel("metrics")
|
134
|
+
alerts_channel = app.create_channel("alerts")
|
135
|
+
|
136
|
+
def handle_metric_report(message: Message):
|
137
|
+
metric_name = message.payload.get('metric', 'unknown')
|
138
|
+
value = message.payload.get('value', 0)
|
139
|
+
print(f"[monitoring:metrics] Metric {metric_name}: {value}")
|
140
|
+
|
141
|
+
# Check for alerts
|
142
|
+
if metric_name == "cpu_usage" and value > 80:
|
143
|
+
alert_msg = Message(
|
144
|
+
message_id="",
|
145
|
+
app_id=message.app_id,
|
146
|
+
channel_id="alerts",
|
147
|
+
message_type=MessageType.NOTIFICATION,
|
148
|
+
payload={
|
149
|
+
'type': 'high_cpu_alert',
|
150
|
+
'metric': metric_name,
|
151
|
+
'value': value,
|
152
|
+
'threshold': 80,
|
153
|
+
'severity': 'warning'
|
154
|
+
},
|
155
|
+
timestamp=time.time()
|
156
|
+
)
|
157
|
+
server._handle_publish(alert_msg, app.get_channel("alerts"))
|
158
|
+
|
159
|
+
metrics_channel.set_handler(MessageType.NOTIFICATION, handle_metric_report)
|
160
|
+
|
161
|
+
|
162
|
+
def main():
|
163
|
+
# Create and configure the server
|
164
|
+
server = FrameworkServer(host="localhost", port=8888)
|
165
|
+
|
166
|
+
print("Setting up IPC Framework Server...")
|
167
|
+
|
168
|
+
# Setup different applications
|
169
|
+
setup_chat_application(server)
|
170
|
+
setup_file_sharing_application(server)
|
171
|
+
setup_monitoring_application(server)
|
172
|
+
|
173
|
+
print("Applications configured:")
|
174
|
+
for app_id, app_info in server.list_applications().items():
|
175
|
+
print(f" - {app_id} ({app_info['name']}): {len(app_info['channels'])} channels")
|
176
|
+
for channel in app_info['channels']:
|
177
|
+
print(f" * {channel}")
|
178
|
+
|
179
|
+
# Start the server
|
180
|
+
server.start()
|
181
|
+
|
182
|
+
try:
|
183
|
+
print("\nServer is running. Press Ctrl+C to stop.")
|
184
|
+
print("You can now run client examples to interact with the server.")
|
185
|
+
print("\nServer Statistics:")
|
186
|
+
|
187
|
+
while True:
|
188
|
+
time.sleep(10)
|
189
|
+
stats = server.get_stats()
|
190
|
+
apps = server.list_applications()
|
191
|
+
|
192
|
+
print(f"\n--- Server Stats ---")
|
193
|
+
print(f"Active connections: {stats['total_connections']}")
|
194
|
+
print(f"Active applications: {stats['total_applications']}")
|
195
|
+
|
196
|
+
for app_id, app_info in apps.items():
|
197
|
+
channels_info = server.list_channels(app_id)
|
198
|
+
print(f" {app_id}: {app_info['connections']} connections")
|
199
|
+
for channel_id, channel_info in channels_info.items():
|
200
|
+
print(f" {channel_id}: {channel_info['subscribers']} subscribers")
|
201
|
+
|
202
|
+
except KeyboardInterrupt:
|
203
|
+
print("\nShutting down server...")
|
204
|
+
server.stop()
|
205
|
+
print("Server stopped.")
|
206
|
+
|
207
|
+
|
208
|
+
if __name__ == "__main__":
|
209
|
+
main()
|
@@ -0,0 +1,168 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Chat Client Example
|
4
|
+
|
5
|
+
This example demonstrates how to create a chat client using the IPC framework.
|
6
|
+
It connects to the chat_app application and can send/receive messages in different channels.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import time
|
10
|
+
import threading
|
11
|
+
import sys
|
12
|
+
from .. import FrameworkClient, Message, MessageType
|
13
|
+
|
14
|
+
|
15
|
+
class ChatClient:
|
16
|
+
def __init__(self, username: str, host: str = "localhost", port: int = 8888):
|
17
|
+
self.username = username
|
18
|
+
self.client = FrameworkClient("chat_app", host, port)
|
19
|
+
self.current_channel = "general"
|
20
|
+
self.running = False
|
21
|
+
|
22
|
+
def connect(self):
|
23
|
+
"""Connect to the chat server"""
|
24
|
+
if self.client.connect():
|
25
|
+
print(f"Connected as {self.username}")
|
26
|
+
|
27
|
+
# Subscribe to all chat channels
|
28
|
+
self.subscribe_to_channel("general")
|
29
|
+
self.subscribe_to_channel("tech_talk")
|
30
|
+
self.subscribe_to_channel("random")
|
31
|
+
|
32
|
+
# Set current channel
|
33
|
+
self.switch_channel("general")
|
34
|
+
return True
|
35
|
+
else:
|
36
|
+
print("Failed to connect to server")
|
37
|
+
return False
|
38
|
+
|
39
|
+
def subscribe_to_channel(self, channel_id: str):
|
40
|
+
"""Subscribe to a chat channel"""
|
41
|
+
def message_handler(message: Message):
|
42
|
+
payload = message.payload
|
43
|
+
msg_type = payload.get('type', 'unknown')
|
44
|
+
|
45
|
+
if msg_type == 'chat_message':
|
46
|
+
username = payload.get('username', 'Unknown')
|
47
|
+
text = payload.get('text', '')
|
48
|
+
if username != self.username: # Don't show our own messages
|
49
|
+
print(f"\n[{channel_id}] {username}: {text}")
|
50
|
+
self.show_prompt()
|
51
|
+
|
52
|
+
elif msg_type == 'user_joined':
|
53
|
+
username = payload.get('username', 'Unknown')
|
54
|
+
if username != self.username:
|
55
|
+
print(f"\n[{channel_id}] *** {username} joined the channel ***")
|
56
|
+
self.show_prompt()
|
57
|
+
|
58
|
+
self.client.subscribe(channel_id, message_handler)
|
59
|
+
|
60
|
+
# Send join notification
|
61
|
+
self.client.notify(channel_id, {
|
62
|
+
'type': 'user_join',
|
63
|
+
'username': self.username
|
64
|
+
})
|
65
|
+
|
66
|
+
def switch_channel(self, channel_id: str):
|
67
|
+
"""Switch to a different channel"""
|
68
|
+
self.current_channel = channel_id
|
69
|
+
print(f"Switched to #{channel_id}")
|
70
|
+
|
71
|
+
# Send join notification to new channel
|
72
|
+
self.client.notify(channel_id, {
|
73
|
+
'type': 'user_join',
|
74
|
+
'username': self.username
|
75
|
+
})
|
76
|
+
|
77
|
+
def send_message(self, text: str):
|
78
|
+
"""Send a chat message to current channel"""
|
79
|
+
self.client.request(self.current_channel, {
|
80
|
+
'username': self.username,
|
81
|
+
'text': text,
|
82
|
+
'connection_id': self.client.connection_id
|
83
|
+
})
|
84
|
+
|
85
|
+
def show_prompt(self):
|
86
|
+
"""Show the chat prompt"""
|
87
|
+
if self.running:
|
88
|
+
print(f"[{self.current_channel}] {self.username}: ", end="", flush=True)
|
89
|
+
|
90
|
+
def start_chat(self):
|
91
|
+
"""Start the chat interface"""
|
92
|
+
self.running = True
|
93
|
+
|
94
|
+
print("\n=== Chat Client ===")
|
95
|
+
print("Commands:")
|
96
|
+
print(" /join <channel> - Switch to a channel (general, tech_talk, random)")
|
97
|
+
print(" /quit - Exit the chat")
|
98
|
+
print(" <message> - Send a message to current channel")
|
99
|
+
print(f"\nYou are in #{self.current_channel}")
|
100
|
+
|
101
|
+
try:
|
102
|
+
while self.running:
|
103
|
+
self.show_prompt()
|
104
|
+
user_input = input().strip()
|
105
|
+
|
106
|
+
if not user_input:
|
107
|
+
continue
|
108
|
+
|
109
|
+
if user_input.startswith('/'):
|
110
|
+
self.handle_command(user_input)
|
111
|
+
else:
|
112
|
+
self.send_message(user_input)
|
113
|
+
print(f"[{self.current_channel}] {self.username}: {user_input}")
|
114
|
+
|
115
|
+
except KeyboardInterrupt:
|
116
|
+
pass
|
117
|
+
finally:
|
118
|
+
self.disconnect()
|
119
|
+
|
120
|
+
def handle_command(self, command: str):
|
121
|
+
"""Handle chat commands"""
|
122
|
+
parts = command.split()
|
123
|
+
cmd = parts[0].lower()
|
124
|
+
|
125
|
+
if cmd == '/quit':
|
126
|
+
self.running = False
|
127
|
+
|
128
|
+
elif cmd == '/join' and len(parts) > 1:
|
129
|
+
channel = parts[1]
|
130
|
+
if channel in ['general', 'tech_talk', 'random']:
|
131
|
+
self.switch_channel(channel)
|
132
|
+
else:
|
133
|
+
print("Available channels: general, tech_talk, random")
|
134
|
+
|
135
|
+
elif cmd == '/channels':
|
136
|
+
print("Available channels: general, tech_talk, random")
|
137
|
+
print(f"Current channel: {self.current_channel}")
|
138
|
+
|
139
|
+
elif cmd == '/who':
|
140
|
+
print(f"You are {self.username} in #{self.current_channel}")
|
141
|
+
|
142
|
+
else:
|
143
|
+
print("Unknown command. Available commands: /join, /channels, /who, /quit")
|
144
|
+
|
145
|
+
def disconnect(self):
|
146
|
+
"""Disconnect from the server"""
|
147
|
+
self.running = False
|
148
|
+
self.client.disconnect()
|
149
|
+
print("\nDisconnected from chat server.")
|
150
|
+
|
151
|
+
|
152
|
+
def main():
|
153
|
+
if len(sys.argv) != 2:
|
154
|
+
print("Usage: python chat_client.py <username>")
|
155
|
+
sys.exit(1)
|
156
|
+
|
157
|
+
username = sys.argv[1]
|
158
|
+
client = ChatClient(username)
|
159
|
+
|
160
|
+
if client.connect():
|
161
|
+
client.start_chat()
|
162
|
+
else:
|
163
|
+
print("Failed to connect to server. Make sure the server is running.")
|
164
|
+
sys.exit(1)
|
165
|
+
|
166
|
+
|
167
|
+
if __name__ == "__main__":
|
168
|
+
main()
|