consolle 0.2.6

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.
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "json"
5
+ require "logger"
6
+ require "fileutils"
7
+ require_relative "console_supervisor"
8
+ require_relative "request_broker"
9
+
10
+ module Consolle
11
+ module Server
12
+ class ConsoleSocketServer
13
+ attr_reader :socket_path, :logger
14
+
15
+ def initialize(socket_path:, rails_root:, rails_env: "development", logger: nil, command: nil)
16
+ @socket_path = socket_path
17
+ @rails_root = rails_root
18
+ @rails_env = rails_env
19
+ @command = command || "bin/rails console"
20
+ @logger = logger || begin
21
+ log = Logger.new(STDOUT)
22
+ log.level = Logger::DEBUG
23
+ log
24
+ end
25
+ @running = false
26
+ @server = nil
27
+ @supervisor = nil
28
+ @broker = nil
29
+ @accept_thread = nil
30
+ end
31
+
32
+ def start
33
+ return false if @running
34
+
35
+ setup_socket
36
+ setup_supervisor
37
+ setup_broker
38
+ setup_signal_handlers
39
+
40
+ @running = true
41
+ @accept_thread = start_accept_loop
42
+
43
+ logger.info "[ConsoleSocketServer] Started at #{@socket_path}"
44
+ true
45
+ rescue StandardError => e
46
+ logger.error "[ConsoleSocketServer] Failed to start: #{e.message}"
47
+ cleanup
48
+ raise
49
+ end
50
+
51
+ def stop
52
+ return false unless @running
53
+
54
+ @running = false
55
+
56
+ # Stop accepting new connections
57
+ @server&.close rescue nil
58
+ @accept_thread&.join(5)
59
+
60
+ # Stop broker
61
+ @broker&.stop
62
+
63
+ # Stop supervisor
64
+ @supervisor&.stop
65
+
66
+ # Clean up socket file
67
+ File.unlink(@socket_path) if File.exist?(@socket_path)
68
+
69
+ logger.info "[ConsoleSocketServer] Stopped"
70
+ true
71
+ end
72
+
73
+ def running?
74
+ @running && @supervisor&.running?
75
+ end
76
+
77
+ private
78
+
79
+ def setup_socket
80
+ # Ensure socket directory exists
81
+ socket_dir = File.dirname(@socket_path)
82
+ FileUtils.mkdir_p(socket_dir) unless Dir.exist?(socket_dir)
83
+
84
+ # Remove existing socket file
85
+ File.unlink(@socket_path) if File.exist?(@socket_path)
86
+
87
+ # Create Unix socket
88
+ @server = UNIXServer.new(@socket_path)
89
+
90
+ # Set permissions (owner only)
91
+ File.chmod(0600, @socket_path)
92
+ end
93
+
94
+ def setup_supervisor
95
+ @supervisor = ConsoleSupervisor.new(
96
+ rails_root: @rails_root,
97
+ rails_env: @rails_env,
98
+ logger: @logger,
99
+ command: @command
100
+ )
101
+ end
102
+
103
+ def setup_broker
104
+ @broker = RequestBroker.new(
105
+ supervisor: @supervisor,
106
+ logger: @logger
107
+ )
108
+ @broker.start
109
+ end
110
+
111
+ def setup_signal_handlers
112
+ %w[INT TERM].each do |signal|
113
+ Signal.trap(signal) do
114
+ # Don't use logger in signal handler - it's not safe
115
+ @running = false
116
+ exit(0)
117
+ end
118
+ end
119
+ end
120
+
121
+ def start_accept_loop
122
+ Thread.new do
123
+ while @running
124
+ begin
125
+ client = @server.accept
126
+ handle_client(client)
127
+ rescue IOError => e
128
+ # Socket closed, expected during shutdown
129
+ break unless @running
130
+ logger.error "[ConsoleSocketServer] Accept error: #{e.message}"
131
+ rescue StandardError => e
132
+ logger.error "[ConsoleSocketServer] Unexpected error: #{e.message}"
133
+ logger.error e.backtrace.join("\n")
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ def handle_client(client)
140
+ Thread.new do
141
+ begin
142
+ # Read request
143
+ request_data = client.gets
144
+ return unless request_data
145
+
146
+ request = JSON.parse(request_data)
147
+ logger.debug "[ConsoleSocketServer] Request: #{request.inspect}"
148
+
149
+ # Process through broker
150
+ response = @broker.process_request(request)
151
+
152
+ # Send response
153
+ begin
154
+ client.write(JSON.generate(response))
155
+ client.write("\n")
156
+ client.flush
157
+ rescue Errno::EPIPE
158
+ # Client disconnected before we could send response
159
+ logger.debug "[ConsoleSocketServer] Client disconnected before response could be sent"
160
+ end
161
+ rescue JSON::ParserError => e
162
+ begin
163
+ error_response = {
164
+ "success" => false,
165
+ "error" => "InvalidRequest",
166
+ "message" => "Invalid JSON: #{e.message}"
167
+ }
168
+ client.write(JSON.generate(error_response))
169
+ client.write("\n")
170
+ rescue Errno::EPIPE
171
+ logger.debug "[ConsoleSocketServer] Client disconnected while sending JSON parse error"
172
+ end
173
+ rescue Errno::EPIPE => e
174
+ # Client disconnected, ignore
175
+ logger.debug "[ConsoleSocketServer] Client disconnected (Broken pipe)"
176
+ rescue StandardError => e
177
+ logger.error "[ConsoleSocketServer] Client handler error: #{e.message}"
178
+ begin
179
+ error_response = {
180
+ "success" => false,
181
+ "error" => e.class.name,
182
+ "message" => e.message
183
+ }
184
+ client.write(JSON.generate(error_response))
185
+ client.write("\n")
186
+ rescue Errno::EPIPE
187
+ # Client disconnected while sending error response
188
+ logger.debug "[ConsoleSocketServer] Client disconnected while sending error response"
189
+ end
190
+ ensure
191
+ client.close rescue nil
192
+ end
193
+ end
194
+ end
195
+
196
+ def cleanup
197
+ stop
198
+ end
199
+ end
200
+ end
201
+ end