smart_message 0.0.12 → 0.0.16
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +155 -1
- data/Gemfile.lock +6 -6
- data/README.md +71 -25
- data/docs/core-concepts/architecture.md +5 -10
- data/docs/getting-started/examples.md +0 -12
- data/docs/getting-started/quick-start.md +4 -9
- data/docs/index.md +6 -4
- data/docs/reference/serializers.md +160 -488
- data/docs/reference/transports.md +47 -146
- data/docs/transports/memory-transport.md +2 -1
- data/docs/transports/multi-transport.md +484 -0
- data/docs/transports/redis-transport-comparison.md +215 -350
- data/docs/transports/redis-transport.md +3 -22
- data/examples/README.md +6 -9
- data/examples/city_scenario/README.md +1 -1
- data/examples/city_scenario/messages/emergency_911_message.rb +0 -1
- data/examples/city_scenario/messages/emergency_resolved_message.rb +0 -1
- data/examples/city_scenario/messages/fire_dispatch_message.rb +0 -1
- data/examples/city_scenario/messages/fire_emergency_message.rb +0 -1
- data/examples/city_scenario/messages/health_check_message.rb +0 -1
- data/examples/city_scenario/messages/health_status_message.rb +0 -1
- data/examples/city_scenario/messages/police_dispatch_message.rb +0 -1
- data/examples/city_scenario/messages/silent_alarm_message.rb +0 -1
- data/examples/file/00_run_all_file_demos.rb +260 -0
- data/examples/file/01_basic_file_transport_demo.rb +237 -0
- data/examples/file/02_fifo_transport_demo.rb +289 -0
- data/examples/file/03_file_watching_demo.rb +332 -0
- data/examples/file/04_multi_transport_file_demo.rb +432 -0
- data/examples/file/README.md +257 -0
- data/examples/memory/00_run_all_demos.rb +317 -0
- data/examples/memory/01_message_deduplication_demo.rb +18 -32
- data/examples/memory/02_dead_letter_queue_demo.rb +9 -12
- data/examples/memory/03_point_to_point_orders.rb +3 -5
- data/examples/memory/04_publish_subscribe_events.rb +15 -16
- data/examples/memory/05_many_to_many_chat.rb +19 -22
- data/examples/memory/06_stdout_publish_only.rb +145 -0
- data/examples/memory/07_proc_handlers_demo.rb +13 -14
- data/examples/memory/08_custom_logger_demo.rb +136 -140
- data/examples/memory/09_error_handling_demo.rb +7 -10
- data/examples/memory/10_entity_addressing_basic.rb +25 -31
- data/examples/memory/11_entity_addressing_with_filtering.rb +32 -36
- data/examples/memory/12_regex_filtering_microservices.rb +10 -11
- data/examples/memory/13_header_block_configuration.rb +0 -5
- data/examples/memory/14_global_configuration_demo.rb +12 -14
- data/examples/memory/15_logger_demo.rb +0 -1
- data/examples/memory/README.md +37 -20
- data/examples/memory/log/demo_app.log.1 +100 -0
- data/examples/memory/log/demo_app.log.2 +100 -0
- data/examples/multi_transport_example.rb +114 -0
- data/examples/redis/01_smart_home_iot_demo.rb +20 -24
- data/examples/redis/README.md +0 -2
- data/examples/utilities/box_it.rb +12 -0
- data/examples/utilities/doing.rb +19 -0
- data/examples/utilities/temp.md +28 -0
- data/lib/smart_message/base.rb +24 -17
- data/lib/smart_message/configuration.rb +2 -23
- data/lib/smart_message/dead_letter_queue.rb +1 -1
- data/lib/smart_message/errors.rb +3 -0
- data/lib/smart_message/header.rb +1 -1
- data/lib/smart_message/logger/default.rb +1 -1
- data/lib/smart_message/messaging.rb +37 -66
- data/lib/smart_message/plugins.rb +42 -41
- data/lib/smart_message/serializer/base.rb +1 -1
- data/lib/smart_message/serializer.rb +3 -2
- data/lib/smart_message/subscription.rb +18 -20
- data/lib/smart_message/transport/async_publish_queue.rb +284 -0
- data/lib/smart_message/transport/base.rb +42 -8
- data/lib/smart_message/transport/fifo_operations.rb +264 -0
- data/lib/smart_message/transport/file_operations.rb +200 -0
- data/lib/smart_message/transport/file_transport.rb +149 -0
- data/lib/smart_message/transport/file_watching.rb +72 -0
- data/lib/smart_message/transport/memory_transport.rb +23 -4
- data/lib/smart_message/transport/partitioned_files.rb +46 -0
- data/lib/smart_message/transport/redis_transport.rb +11 -0
- data/lib/smart_message/transport/registry.rb +0 -1
- data/lib/smart_message/transport/stdout_transport.rb +73 -41
- data/lib/smart_message/transport/stdout_transport.rb.backup +88 -0
- data/lib/smart_message/transport.rb +0 -1
- data/lib/smart_message/version.rb +1 -1
- metadata +25 -37
- data/docs/guides/redis-queue-getting-started.md +0 -697
- data/docs/guides/redis-queue-patterns.md +0 -889
- data/docs/guides/redis-queue-production.md +0 -1091
- data/docs/transports/redis-enhanced-transport.md +0 -524
- data/docs/transports/redis-queue-transport.md +0 -1304
- data/examples/redis_enhanced/README.md +0 -319
- data/examples/redis_enhanced/enhanced_01_basic_patterns.rb +0 -233
- data/examples/redis_enhanced/enhanced_02_fluent_api.rb +0 -331
- data/examples/redis_enhanced/enhanced_03_dual_publishing.rb +0 -281
- data/examples/redis_enhanced/enhanced_04_advanced_routing.rb +0 -419
- data/examples/redis_queue/01_basic_messaging.rb +0 -221
- data/examples/redis_queue/01_comprehensive_examples.rb +0 -508
- data/examples/redis_queue/02_pattern_routing.rb +0 -405
- data/examples/redis_queue/03_fluent_api.rb +0 -422
- data/examples/redis_queue/04_load_balancing.rb +0 -486
- data/examples/redis_queue/05_microservices.rb +0 -735
- data/examples/redis_queue/06_emergency_alerts.rb +0 -777
- data/examples/redis_queue/07_queue_management.rb +0 -587
- data/examples/redis_queue/README.md +0 -366
- data/examples/redis_queue/enhanced_01_basic_patterns.rb +0 -233
- data/examples/redis_queue/enhanced_02_fluent_api.rb +0 -331
- data/examples/redis_queue/enhanced_03_dual_publishing.rb +0 -281
- data/examples/redis_queue/enhanced_04_advanced_routing.rb +0 -419
- data/examples/redis_queue/redis_queue_architecture.svg +0 -148
- data/ideas/README.md +0 -41
- data/ideas/agents.md +0 -1001
- data/ideas/database_transport.md +0 -980
- data/ideas/improvement.md +0 -359
- data/ideas/meshage.md +0 -1788
- data/ideas/message_discovery.md +0 -178
- data/ideas/message_schema.md +0 -1381
- data/lib/smart_message/transport/redis_enhanced_transport.rb +0 -399
- data/lib/smart_message/transport/redis_queue_transport.rb +0 -555
- data/lib/smart_message/wrapper.rb.bak +0 -132
- /data/examples/memory/{06_pretty_print_demo.rb → 16_pretty_print_demo.rb} +0 -0
@@ -0,0 +1,200 @@
|
|
1
|
+
# lib/smart_message/transport/file_operations.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module SmartMessage
|
6
|
+
module Transport
|
7
|
+
# Module for shared file operations, including buffering, rotation, and basic I/O.
|
8
|
+
module FileOperations
|
9
|
+
def configure_file_output
|
10
|
+
ensure_directory_exists if @options[:create_directories]
|
11
|
+
@file_handle = open_file_handle
|
12
|
+
@write_buffer = []
|
13
|
+
@last_flush = Time.now
|
14
|
+
setup_rotation_timer if rotation_enabled?
|
15
|
+
@file_mutex = Mutex.new # For thread-safety
|
16
|
+
end
|
17
|
+
|
18
|
+
def write_to_file(serialized_message)
|
19
|
+
content = prepare_file_content(serialized_message)
|
20
|
+
|
21
|
+
@file_mutex.synchronize do
|
22
|
+
if buffered_mode?
|
23
|
+
buffer_write(content)
|
24
|
+
else
|
25
|
+
direct_write(content)
|
26
|
+
end
|
27
|
+
|
28
|
+
rotate_file_if_needed
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def flush_buffer
|
33
|
+
return if @write_buffer.empty?
|
34
|
+
|
35
|
+
# Only synchronize if we're not already holding the lock
|
36
|
+
if @file_mutex.owned?
|
37
|
+
@file_handle.write(@write_buffer.join)
|
38
|
+
@file_handle.flush
|
39
|
+
@write_buffer.clear
|
40
|
+
@last_flush = Time.now
|
41
|
+
else
|
42
|
+
@file_mutex.synchronize do
|
43
|
+
@file_handle.write(@write_buffer.join)
|
44
|
+
@file_handle.flush
|
45
|
+
@write_buffer.clear
|
46
|
+
@last_flush = Time.now
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def close_file_handle
|
52
|
+
flush_buffer if buffered_mode?
|
53
|
+
if @file_mutex
|
54
|
+
@file_mutex.synchronize do
|
55
|
+
@file_handle&.close
|
56
|
+
@file_handle = nil
|
57
|
+
end
|
58
|
+
else
|
59
|
+
@file_handle&.close
|
60
|
+
@file_handle = nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def prepare_file_content(serialized_message)
|
67
|
+
case @options[:file_format]
|
68
|
+
when :lines
|
69
|
+
"#{serialized_message}\n"
|
70
|
+
when :raw
|
71
|
+
serialized_message
|
72
|
+
else
|
73
|
+
"#{serialized_message}\n" # default to lines
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def open_file_handle
|
78
|
+
File.open(current_file_path, file_mode, encoding: @options[:encoding])
|
79
|
+
end
|
80
|
+
|
81
|
+
def ensure_directory_exists
|
82
|
+
return unless @options[:create_directories]
|
83
|
+
|
84
|
+
dir = File.dirname(current_file_path)
|
85
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
86
|
+
end
|
87
|
+
|
88
|
+
def current_file_path
|
89
|
+
if rotation_enabled? && time_based_rotation?
|
90
|
+
timestamped_file_path
|
91
|
+
else
|
92
|
+
@options[:file_path]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def file_mode
|
97
|
+
@options[:file_mode] || 'a' # append by default
|
98
|
+
end
|
99
|
+
|
100
|
+
def buffered_mode?
|
101
|
+
@options[:buffer_size] && @options[:buffer_size] > 0
|
102
|
+
end
|
103
|
+
|
104
|
+
def buffer_write(content)
|
105
|
+
@write_buffer << content
|
106
|
+
|
107
|
+
if buffer_full? || flush_interval_exceeded?
|
108
|
+
flush_buffer
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def direct_write(content)
|
113
|
+
@file_handle.write(content)
|
114
|
+
@file_handle.flush if @options[:auto_flush]
|
115
|
+
end
|
116
|
+
|
117
|
+
def buffer_full?
|
118
|
+
@write_buffer.join.bytesize >= @options[:buffer_size]
|
119
|
+
end
|
120
|
+
|
121
|
+
def flush_interval_exceeded?
|
122
|
+
@options[:flush_interval] &&
|
123
|
+
(Time.now - @last_flush) >= @options[:flush_interval]
|
124
|
+
end
|
125
|
+
|
126
|
+
def rotation_enabled?
|
127
|
+
@options[:rotate_size] || @options[:rotate_time]
|
128
|
+
end
|
129
|
+
|
130
|
+
def time_based_rotation?
|
131
|
+
@options[:rotate_time]
|
132
|
+
end
|
133
|
+
|
134
|
+
def should_rotate?
|
135
|
+
size_rotation_needed? || time_rotation_needed?
|
136
|
+
end
|
137
|
+
|
138
|
+
def size_rotation_needed?
|
139
|
+
@options[:rotate_size] &&
|
140
|
+
File.exist?(current_file_path) && File.size(current_file_path) >= @options[:rotate_size]
|
141
|
+
end
|
142
|
+
|
143
|
+
def time_rotation_needed?
|
144
|
+
return false unless @options[:rotate_time]
|
145
|
+
|
146
|
+
case @options[:rotate_time]
|
147
|
+
when :hourly
|
148
|
+
Time.now.min == 0 && Time.now.sec == 0
|
149
|
+
when :daily
|
150
|
+
Time.now.hour == 0 && Time.now.min == 0 && Time.now.sec == 0
|
151
|
+
else
|
152
|
+
false
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def rotate_file_if_needed
|
157
|
+
return unless should_rotate?
|
158
|
+
|
159
|
+
close_current_file
|
160
|
+
archive_current_file
|
161
|
+
@file_handle = open_file_handle
|
162
|
+
end
|
163
|
+
|
164
|
+
def close_current_file
|
165
|
+
flush_buffer if buffered_mode?
|
166
|
+
@file_handle&.close
|
167
|
+
@file_handle = nil
|
168
|
+
end
|
169
|
+
|
170
|
+
def archive_current_file
|
171
|
+
return unless File.exist?(current_file_path)
|
172
|
+
|
173
|
+
timestamp = Time.now.strftime(@options[:timestamp_format] || '%Y%m%d_%H%M%S')
|
174
|
+
base = File.basename(@options[:file_path], '.*')
|
175
|
+
ext = File.extname(@options[:file_path])
|
176
|
+
dir = File.dirname(@options[:file_path])
|
177
|
+
archive_path = File.join(dir, "#{base}_#{timestamp}#{ext}")
|
178
|
+
|
179
|
+
FileUtils.mv(current_file_path, archive_path)
|
180
|
+
|
181
|
+
# Maintain rotation count
|
182
|
+
if @options[:rotate_count]
|
183
|
+
files = Dir.glob(File.join(dir, "#{base}_*#{ext}")).sort
|
184
|
+
while files.size > @options[:rotate_count]
|
185
|
+
File.delete(files.shift)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def timestamped_file_path
|
191
|
+
base = File.basename(@options[:file_path], '.*')
|
192
|
+
ext = File.extname(@options[:file_path])
|
193
|
+
dir = File.dirname(@options[:file_path])
|
194
|
+
timestamp = Time.now.strftime(@options[:timestamp_format] || '%Y%m%d_%H%M%S')
|
195
|
+
|
196
|
+
File.join(dir, "#{base}_#{timestamp}#{ext}")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# lib/smart_message/transport/file_transport.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require_relative 'file_operations'
|
6
|
+
require_relative 'file_watching'
|
7
|
+
require_relative 'partitioned_files'
|
8
|
+
require_relative 'async_publish_queue'
|
9
|
+
require_relative 'fifo_operations'
|
10
|
+
|
11
|
+
module SmartMessage
|
12
|
+
module Transport
|
13
|
+
class FileTransport < Base
|
14
|
+
include FileOperations
|
15
|
+
include FileWatching
|
16
|
+
include PartitionedFiles
|
17
|
+
include AsyncPublishQueue
|
18
|
+
include FifoOperations
|
19
|
+
|
20
|
+
# @param path [String, IO, Pathname] file path or IO-like object
|
21
|
+
# @param mode [String] file open mode ("a" for append, etc.)
|
22
|
+
# @param encoding [String, nil] file encoding
|
23
|
+
def initialize(options = {})
|
24
|
+
@current_message_class = nil
|
25
|
+
super(**options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_options
|
29
|
+
{
|
30
|
+
file_path: 'messages.log',
|
31
|
+
file_mode: 'a',
|
32
|
+
encoding: nil,
|
33
|
+
file_format: :lines,
|
34
|
+
buffer_size: 0,
|
35
|
+
flush_interval: nil,
|
36
|
+
auto_flush: true,
|
37
|
+
rotate_size: nil,
|
38
|
+
rotate_time: nil,
|
39
|
+
rotate_count: 5,
|
40
|
+
timestamp_format: '%Y%m%d_%H%M%S',
|
41
|
+
create_directories: true,
|
42
|
+
async: false,
|
43
|
+
max_queue: nil,
|
44
|
+
drop_when_full: false,
|
45
|
+
queue_overflow_strategy: :block,
|
46
|
+
max_retries: 3,
|
47
|
+
max_retry_delay: 30,
|
48
|
+
worker_timeout: 5,
|
49
|
+
shutdown_timeout: 10,
|
50
|
+
queue_warning_threshold: 0.8,
|
51
|
+
enable_queue_monitoring: true,
|
52
|
+
drain_queue_on_shutdown: true,
|
53
|
+
send_dropped_to_dlq: false,
|
54
|
+
read_from_end: true,
|
55
|
+
poll_interval: 1.0,
|
56
|
+
file_type: :regular,
|
57
|
+
create_fifo: false,
|
58
|
+
fifo_mode: :blocking,
|
59
|
+
fifo_permissions: 0644,
|
60
|
+
fallback_transport: nil,
|
61
|
+
enable_subscriptions: false,
|
62
|
+
subscription_mode: :polling,
|
63
|
+
filename_selector: nil,
|
64
|
+
directory: nil,
|
65
|
+
subscription_file_path: nil
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def configure
|
70
|
+
# Call parent configuration first
|
71
|
+
super if defined?(super)
|
72
|
+
|
73
|
+
# Then configure our file-specific features
|
74
|
+
if @options[:async]
|
75
|
+
configure_async_publishing
|
76
|
+
elsif @options[:file_type] == :fifo
|
77
|
+
configure_fifo
|
78
|
+
else
|
79
|
+
configure_file_output
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def publish(payload)
|
84
|
+
do_publish(nil, payload)
|
85
|
+
end
|
86
|
+
|
87
|
+
def do_publish(message_class, serialized_message)
|
88
|
+
@current_message_class = message_class
|
89
|
+
if @options[:async]
|
90
|
+
async_publish(message_class, serialized_message)
|
91
|
+
else
|
92
|
+
if @options[:filename_selector] || @options[:directory]
|
93
|
+
header = { message_class_name: message_class.to_s }
|
94
|
+
path = determine_file_path(serialized_message, header)
|
95
|
+
@file_handle = get_or_open_partition_handle(path)
|
96
|
+
write_to_file(serialized_message)
|
97
|
+
elsif @options[:file_type] == :fifo
|
98
|
+
write_to_fifo(serialized_message)
|
99
|
+
else
|
100
|
+
write_to_file(serialized_message)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def subscribe(message_class, process_method, filter_options = {})
|
106
|
+
unless @options[:enable_subscriptions]
|
107
|
+
logger.warn { "[FileTransport] Subscriptions disabled - set enable_subscriptions: true" }
|
108
|
+
return
|
109
|
+
end
|
110
|
+
|
111
|
+
if @options[:file_type] == :fifo
|
112
|
+
start_fifo_reader(message_class, process_method, filter_options)
|
113
|
+
else
|
114
|
+
start_file_polling(message_class, process_method, filter_options)
|
115
|
+
end
|
116
|
+
|
117
|
+
super(message_class, process_method, filter_options)
|
118
|
+
end
|
119
|
+
|
120
|
+
def connected?
|
121
|
+
case @options[:file_type]
|
122
|
+
when :fifo
|
123
|
+
subscription_active? || fifo_active?
|
124
|
+
else
|
125
|
+
(@file_handle && !@file_handle.closed?) || subscription_active?
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def disconnect
|
130
|
+
stop_file_subscriptions
|
131
|
+
stop_fifo_operations if @options[:file_type] == :fifo
|
132
|
+
stop_async_publishing if @options[:async]
|
133
|
+
close_partition_handles if @options[:filename_selector] || @options[:directory]
|
134
|
+
close_file_handle
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def subscription_active?
|
140
|
+
@polling_thread&.alive? || fifo_active?
|
141
|
+
end
|
142
|
+
|
143
|
+
def stop_file_subscriptions
|
144
|
+
@polling_thread&.kill
|
145
|
+
@polling_thread&.join(5)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# lib/smart_message/transport/file_watching.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module SmartMessage
|
6
|
+
module Transport
|
7
|
+
# Module for file watching and subscription support (reading/tailing).
|
8
|
+
module FileWatching
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def logger
|
13
|
+
# Ensure we have a proper logger, not just an IO object
|
14
|
+
if @logger && @logger.respond_to?(:error) && @logger.respond_to?(:info) && @logger.respond_to?(:warn)
|
15
|
+
return @logger
|
16
|
+
end
|
17
|
+
@logger = SmartMessage::Logger.default
|
18
|
+
end
|
19
|
+
|
20
|
+
public
|
21
|
+
def start_file_polling(message_class, process_method, filter_options)
|
22
|
+
poll_interval = filter_options[:poll_interval] || @options[:poll_interval] || 1.0
|
23
|
+
file_path = subscription_file_path(message_class, filter_options)
|
24
|
+
|
25
|
+
@polling_thread = Thread.new do
|
26
|
+
Thread.current.name = "FileTransport-Poller"
|
27
|
+
last_position = @options[:read_from_end] ? (File.exist?(file_path) ? File.size(file_path) : 0) : 0
|
28
|
+
|
29
|
+
loop do
|
30
|
+
sleep poll_interval
|
31
|
+
next unless File.exist?(file_path)
|
32
|
+
|
33
|
+
current_size = File.size(file_path)
|
34
|
+
if current_size > last_position
|
35
|
+
process_new_file_content(file_path, last_position, current_size, message_class)
|
36
|
+
last_position = current_size
|
37
|
+
end
|
38
|
+
end
|
39
|
+
rescue => e
|
40
|
+
logger.error { "[FileTransport] Polling thread error: #{e.message}" }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def process_new_file_content(file_path, start_pos, end_pos, message_class)
|
45
|
+
File.open(file_path, 'r', encoding: @options[:encoding]) do |file|
|
46
|
+
file.seek(start_pos)
|
47
|
+
content = file.read(end_pos - start_pos)
|
48
|
+
|
49
|
+
content.each_line do |line|
|
50
|
+
next if line.strip.empty?
|
51
|
+
|
52
|
+
begin
|
53
|
+
receive(message_class, line.strip)
|
54
|
+
rescue => e
|
55
|
+
begin
|
56
|
+
logger.error { "[FileTransport] Error processing message: #{e.message}" }
|
57
|
+
rescue
|
58
|
+
# Fallback if logger is not available
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def subscription_file_path(message_class, filter_options)
|
66
|
+
filter_options[:file_path] ||
|
67
|
+
@options[:subscription_file_path] ||
|
68
|
+
File.join(File.dirname(@options[:file_path]), "#{message_class.to_s.downcase}.jsonl")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -16,30 +16,49 @@ module SmartMessage
|
|
16
16
|
}
|
17
17
|
end
|
18
18
|
|
19
|
+
# Memory transport doesn't need serialization
|
20
|
+
def default_serializer
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
19
24
|
def configure
|
20
25
|
@messages = []
|
21
26
|
@message_mutex = Mutex.new
|
22
27
|
end
|
23
28
|
|
24
|
-
#
|
29
|
+
# Implement do_publish for memory transport (no serialization needed)
|
25
30
|
def do_publish(message_class, serialized_message)
|
31
|
+
# For memory transport, serialized_message is actually the message object
|
32
|
+
message = serialized_message
|
33
|
+
|
26
34
|
@message_mutex.synchronize do
|
27
35
|
# Prevent memory overflow
|
28
36
|
@messages.shift if @messages.size >= @options[:max_messages]
|
29
37
|
|
38
|
+
# Store the actual message object, no serialization needed
|
30
39
|
@messages << {
|
31
40
|
message_class: message_class,
|
32
|
-
|
41
|
+
message: message.dup, # Store a copy to prevent mutation
|
33
42
|
published_at: Time.now
|
34
43
|
}
|
35
44
|
end
|
36
45
|
|
37
46
|
# Auto-process if enabled
|
38
47
|
if @options[:auto_process]
|
39
|
-
|
48
|
+
# Route directly without serialization
|
49
|
+
@dispatcher.route(message)
|
40
50
|
end
|
41
51
|
end
|
42
52
|
|
53
|
+
# Override encode_message to return the message object directly
|
54
|
+
def encode_message(message)
|
55
|
+
# Update header with serializer info (even though we don't serialize)
|
56
|
+
message._sm_header.serializer = 'none'
|
57
|
+
|
58
|
+
# Return the message object itself (no encoding needed)
|
59
|
+
message
|
60
|
+
end
|
61
|
+
|
43
62
|
# Get all stored messages
|
44
63
|
def all_messages
|
45
64
|
@message_mutex.synchronize { @messages.dup }
|
@@ -59,7 +78,7 @@ module SmartMessage
|
|
59
78
|
def process_all
|
60
79
|
messages_to_process = @message_mutex.synchronize { @messages.dup }
|
61
80
|
messages_to_process.each do |msg|
|
62
|
-
|
81
|
+
@dispatcher.route(msg[:message])
|
63
82
|
end
|
64
83
|
end
|
65
84
|
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# lib/smart_message/transport/partitioned_files.rb
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module SmartMessage
|
6
|
+
module Transport
|
7
|
+
# Module for fan-out/partitioned file support.
|
8
|
+
module PartitionedFiles
|
9
|
+
def determine_file_path(payload, header)
|
10
|
+
if @options[:filename_selector]&.respond_to?(:call)
|
11
|
+
@options[:filename_selector].call(payload, header)
|
12
|
+
elsif @options[:directory]
|
13
|
+
File.join(@options[:directory], "#{header[:message_class_name].to_s.downcase}.log")
|
14
|
+
else
|
15
|
+
@options[:file_path]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_or_open_partition_handle(full_path)
|
20
|
+
@partition_handles ||= {}
|
21
|
+
@partition_mutexes ||= {}
|
22
|
+
|
23
|
+
unless @partition_handles[full_path]
|
24
|
+
ensure_directory_exists_for(full_path)
|
25
|
+
@partition_handles[full_path] = File.open(full_path, file_mode, encoding: @options[:encoding])
|
26
|
+
@partition_mutexes[full_path] = Mutex.new
|
27
|
+
end
|
28
|
+
|
29
|
+
@partition_handles[full_path]
|
30
|
+
end
|
31
|
+
|
32
|
+
def ensure_directory_exists_for(path)
|
33
|
+
dir = File.dirname(path)
|
34
|
+
FileUtils.mkdir_p(dir) if @options[:create_directories] && !Dir.exist?(dir)
|
35
|
+
end
|
36
|
+
|
37
|
+
def close_partition_handles
|
38
|
+
@partition_handles&.each do |_, handle|
|
39
|
+
handle.close
|
40
|
+
end
|
41
|
+
@partition_handles = {}
|
42
|
+
@partition_mutexes = {}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -24,6 +24,17 @@ module SmartMessage
|
|
24
24
|
}
|
25
25
|
end
|
26
26
|
|
27
|
+
# Default to MessagePack for Redis (efficient binary format)
|
28
|
+
def default_serializer
|
29
|
+
# Try MessagePack first, fall back to JSON
|
30
|
+
begin
|
31
|
+
require 'smart_message/serializer/message_pack'
|
32
|
+
SmartMessage::Serializer::MessagePack.new
|
33
|
+
rescue LoadError
|
34
|
+
SmartMessage::Serializer::Json.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
27
38
|
def configure
|
28
39
|
@redis_pub = Redis.new(url: @options[:url], db: @options[:db])
|
29
40
|
@redis_sub = Redis.new(url: @options[:url], db: @options[:db])
|
@@ -54,7 +54,6 @@ module SmartMessage
|
|
54
54
|
register(:stdout, SmartMessage::Transport::StdoutTransport)
|
55
55
|
register(:memory, SmartMessage::Transport::MemoryTransport)
|
56
56
|
register(:redis, SmartMessage::Transport::RedisTransport)
|
57
|
-
register(:redis_queue, SmartMessage::Transport::RedisQueueTransport)
|
58
57
|
end
|
59
58
|
end
|
60
59
|
end
|