activematrix 0.0.0 → 0.0.2
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/README.md +218 -51
- data/lib/active_matrix/agent_manager.rb +275 -0
- data/lib/active_matrix/agent_registry.rb +154 -0
- data/lib/{matrix_sdk → active_matrix}/api.rb +18 -22
- data/lib/{matrix_sdk → active_matrix}/bot/base.rb +42 -39
- data/lib/{matrix_sdk → active_matrix}/bot/main.rb +4 -5
- data/lib/active_matrix/bot/multi_instance_base.rb +189 -0
- data/lib/active_matrix/bot.rb +7 -0
- data/lib/{matrix_sdk → active_matrix}/client.rb +21 -34
- data/lib/active_matrix/client_pool.rb +194 -0
- data/lib/{matrix_sdk → active_matrix}/errors.rb +4 -4
- data/lib/active_matrix/event_router.rb +215 -0
- data/lib/active_matrix/logging.rb +56 -0
- data/lib/active_matrix/memory/agent_memory.rb +128 -0
- data/lib/active_matrix/memory/base.rb +101 -0
- data/lib/active_matrix/memory/conversation_memory.rb +161 -0
- data/lib/active_matrix/memory/global_memory.rb +153 -0
- data/lib/active_matrix/memory.rb +28 -0
- data/lib/{matrix_sdk → active_matrix}/mxid.rb +2 -2
- data/lib/{matrix_sdk → active_matrix}/protocols/as.rb +1 -1
- data/lib/{matrix_sdk → active_matrix}/protocols/cs.rb +6 -8
- data/lib/{matrix_sdk → active_matrix}/protocols/is.rb +1 -1
- data/lib/{matrix_sdk → active_matrix}/protocols/msc.rb +6 -8
- data/lib/{matrix_sdk → active_matrix}/protocols/ss.rb +2 -2
- data/lib/active_matrix/railtie.rb +18 -0
- data/lib/{matrix_sdk → active_matrix}/response.rb +2 -2
- data/lib/{matrix_sdk → active_matrix}/room.rb +148 -72
- data/lib/{matrix_sdk → active_matrix}/rooms/space.rb +3 -7
- data/lib/{matrix_sdk → active_matrix}/user.rb +23 -15
- data/lib/active_matrix/util/account_data_cache.rb +129 -0
- data/lib/active_matrix/util/cacheable.rb +73 -0
- data/lib/{matrix_sdk → active_matrix}/util/events.rb +8 -8
- data/lib/{matrix_sdk → active_matrix}/util/extensions.rb +6 -15
- data/lib/active_matrix/util/state_event_cache.rb +167 -0
- data/lib/{matrix_sdk → active_matrix}/util/uri.rb +4 -4
- data/lib/active_matrix/version.rb +5 -0
- data/lib/active_matrix.rb +81 -0
- data/lib/generators/active_matrix/bot/bot_generator.rb +38 -0
- data/lib/generators/active_matrix/bot/templates/bot.rb.erb +111 -0
- data/lib/generators/active_matrix/bot/templates/bot_spec.rb.erb +68 -0
- data/lib/generators/active_matrix/install/install_generator.rb +44 -0
- data/lib/generators/active_matrix/install/templates/README +30 -0
- data/lib/generators/active_matrix/install/templates/active_matrix.rb +33 -0
- data/lib/generators/active_matrix/install/templates/agent_memory.rb +47 -0
- data/lib/generators/active_matrix/install/templates/conversation_context.rb +72 -0
- data/lib/generators/active_matrix/install/templates/create_agent_memories.rb +17 -0
- data/lib/generators/active_matrix/install/templates/create_conversation_contexts.rb +21 -0
- data/lib/generators/active_matrix/install/templates/create_global_memories.rb +20 -0
- data/lib/generators/active_matrix/install/templates/create_matrix_agents.rb +26 -0
- data/lib/generators/active_matrix/install/templates/global_memory.rb +70 -0
- data/lib/generators/active_matrix/install/templates/matrix_agent.rb +127 -0
- metadata +168 -30
- data/lib/matrix_sdk/bot.rb +0 -4
- data/lib/matrix_sdk/util/account_data_cache.rb +0 -91
- data/lib/matrix_sdk/util/state_event_cache.rb +0 -92
- data/lib/matrix_sdk/util/tinycache.rb +0 -140
- data/lib/matrix_sdk/util/tinycache_adapter.rb +0 -87
- data/lib/matrix_sdk/version.rb +0 -5
- data/lib/matrix_sdk.rb +0 -75
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveMatrix::Bot
|
4
|
+
# Base class for multi-instance bot support
|
5
|
+
class MultiInstanceBase < Base
|
6
|
+
attr_reader :agent_record
|
7
|
+
|
8
|
+
def initialize(client_or_agent, **params)
|
9
|
+
# Handle both client and agent record initialization
|
10
|
+
if client_or_agent.respond_to?(:homeserver) && !client_or_agent.respond_to?(:client) # It's a client
|
11
|
+
super
|
12
|
+
@agent_record = params[:agent_record]
|
13
|
+
else # It's an agent record
|
14
|
+
@agent_record = client_or_agent
|
15
|
+
super(@agent_record.client, **params)
|
16
|
+
end
|
17
|
+
|
18
|
+
setup_agent_context if @agent_record
|
19
|
+
end
|
20
|
+
|
21
|
+
# Access agent-specific memory
|
22
|
+
def memory
|
23
|
+
@memory ||= ActiveMatrix::Memory.for_agent(@agent_record)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Access conversation memory
|
27
|
+
def conversation_memory(user_id = nil, room_id = nil)
|
28
|
+
user_id ||= event[:sender] if in_event?
|
29
|
+
room_id ||= event[:room_id] if in_event?
|
30
|
+
|
31
|
+
return nil unless user_id && room_id
|
32
|
+
|
33
|
+
@conversation_memories ||= {}
|
34
|
+
@conversation_memories["#{user_id}/#{room_id}"] ||=
|
35
|
+
ActiveMatrix::Memory.for_conversation(@agent_record, user_id, room_id)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Access global memory
|
39
|
+
def global_memory
|
40
|
+
ActiveMatrix::Memory.global
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get current conversation context
|
44
|
+
def conversation_context
|
45
|
+
conversation_memory&.context || {}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Update conversation context
|
49
|
+
def update_context(data)
|
50
|
+
conversation_memory&.update_context(data)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Remember something in conversation
|
54
|
+
def remember_in_conversation(key, &)
|
55
|
+
conversation_memory&.remember(key, &)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Agent state helpers
|
59
|
+
def agent_name
|
60
|
+
@agent_record&.name || settings.bot_name
|
61
|
+
end
|
62
|
+
|
63
|
+
def agent_state
|
64
|
+
@agent_record&.state || 'unknown'
|
65
|
+
end
|
66
|
+
|
67
|
+
def mark_busy!
|
68
|
+
@agent_record&.start_processing! if @agent_record&.may_start_processing?
|
69
|
+
end
|
70
|
+
|
71
|
+
def mark_idle!
|
72
|
+
@agent_record&.finish_processing! if @agent_record&.may_finish_processing?
|
73
|
+
end
|
74
|
+
|
75
|
+
# Inter-agent communication
|
76
|
+
def broadcast_to_agents(selector, data)
|
77
|
+
return unless defined?(AgentRegistry)
|
78
|
+
|
79
|
+
registry = AgentRegistry.instance
|
80
|
+
registry.broadcast_to(selector) do |agent_bot|
|
81
|
+
agent_bot.receive_broadcast(data, from: self)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def send_to_agent(agent_name, data)
|
86
|
+
return unless defined?(AgentRegistry)
|
87
|
+
|
88
|
+
registry = AgentRegistry.instance
|
89
|
+
entry = registry.get_by_name(agent_name)
|
90
|
+
|
91
|
+
if entry
|
92
|
+
entry[:instance].receive_message(data, from: self)
|
93
|
+
true
|
94
|
+
else
|
95
|
+
logger.warn "Agent #{agent_name} not found or not running"
|
96
|
+
false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Receive inter-agent messages (override in subclasses)
|
101
|
+
def receive_message(data, from:)
|
102
|
+
logger.debug "Received message from #{from.agent_name}: #{data.inspect}"
|
103
|
+
end
|
104
|
+
|
105
|
+
def receive_broadcast(data, from:)
|
106
|
+
logger.debug "Received broadcast from #{from.agent_name}: #{data.inspect}"
|
107
|
+
end
|
108
|
+
|
109
|
+
# Override event handling to track conversation
|
110
|
+
def _handle_message(event)
|
111
|
+
# Mark as busy while processing
|
112
|
+
mark_busy!
|
113
|
+
|
114
|
+
# Track conversation if we have an agent record
|
115
|
+
conversation_memory(event[:sender], event[:room_id])&.add_message(event) if @agent_record && event[:type] == 'm.room.message'
|
116
|
+
|
117
|
+
super
|
118
|
+
ensure
|
119
|
+
# Mark as idle when done
|
120
|
+
mark_idle!
|
121
|
+
end
|
122
|
+
|
123
|
+
def _handle_event(event)
|
124
|
+
mark_busy!
|
125
|
+
super
|
126
|
+
ensure
|
127
|
+
mark_idle!
|
128
|
+
end
|
129
|
+
|
130
|
+
# Enhanced settings with agent-specific overrides
|
131
|
+
def settings
|
132
|
+
return self.class unless @agent_record
|
133
|
+
|
134
|
+
# Merge agent-specific settings with class settings
|
135
|
+
@settings ||= begin
|
136
|
+
base_settings = self.class.settings
|
137
|
+
agent_settings = @agent_record.settings || {}
|
138
|
+
|
139
|
+
# Create a settings proxy that checks agent settings first
|
140
|
+
SettingsProxy.new(base_settings, agent_settings)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def setup_agent_context
|
147
|
+
# Set up agent-specific logging
|
148
|
+
if @agent_record
|
149
|
+
@logger = ActiveMatrix.logger.dup
|
150
|
+
@logger.progname = "[#{@agent_record.name}]"
|
151
|
+
end
|
152
|
+
|
153
|
+
# Load any agent-specific configuration
|
154
|
+
return unless @agent_record.settings['commands_disabled']
|
155
|
+
|
156
|
+
@agent_record.settings['commands_disabled'].each do |cmd|
|
157
|
+
# Temporarily disable commands for this instance
|
158
|
+
@disabled_commands ||= []
|
159
|
+
@disabled_commands << cmd
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Settings proxy to merge class and instance settings
|
164
|
+
class SettingsProxy
|
165
|
+
def initialize(base_settings, agent_settings)
|
166
|
+
@base_settings = base_settings
|
167
|
+
@agent_settings = agent_settings
|
168
|
+
end
|
169
|
+
|
170
|
+
def method_missing(method, *, &)
|
171
|
+
method_name = method.to_s.gsub(/\?$/, '')
|
172
|
+
|
173
|
+
# Check agent settings first
|
174
|
+
if @agent_settings.key?(method_name)
|
175
|
+
value = @agent_settings[method_name]
|
176
|
+
return method.to_s.end_with?('?') ? !value.nil? : value
|
177
|
+
end
|
178
|
+
|
179
|
+
# Fall back to base settings
|
180
|
+
@base_settings.send(method, *, &)
|
181
|
+
end
|
182
|
+
|
183
|
+
def respond_to_missing?(method, include_private = false)
|
184
|
+
method_name = method.to_s.gsub(/\?$/, '')
|
185
|
+
@agent_settings.key?(method_name) || @base_settings.respond_to?(method, include_private)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -1,15 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'matrix_sdk'
|
4
|
-
require 'matrix_sdk/util/events'
|
5
|
-
|
6
3
|
require 'English'
|
7
4
|
require 'forwardable'
|
8
5
|
|
9
|
-
module
|
6
|
+
module ActiveMatrix
|
10
7
|
class Client
|
11
|
-
extend
|
12
|
-
include
|
8
|
+
extend ActiveMatrix::Extensions
|
9
|
+
include ActiveMatrix::Logging
|
13
10
|
extend Forwardable
|
14
11
|
|
15
12
|
# @!attribute api [r] The underlying API connection
|
@@ -44,11 +41,11 @@ module MatrixSdk
|
|
44
41
|
# @see Api.new_for_domain
|
45
42
|
# @see #initialize
|
46
43
|
def self.new_for_domain(domain, **params)
|
47
|
-
api =
|
44
|
+
api = ActiveMatrix::Api.new_for_domain(domain, keep_wellknown: true)
|
48
45
|
return new(api, **params) unless api.well_known&.key?('m.identity_server')
|
49
46
|
|
50
|
-
identity_server =
|
51
|
-
new(api, **params
|
47
|
+
identity_server = ActiveMatrix::Api.new(api.well_known['m.identity_server']['base_url'], protocols: %i[IS])
|
48
|
+
new(api, **params, identity_server: identity_server)
|
52
49
|
end
|
53
50
|
|
54
51
|
# @param hs_url [String,URI,Api] The URL to the Matrix homeserver, without the /_matrix/ part, or an existing Api instance
|
@@ -56,7 +53,7 @@ module MatrixSdk
|
|
56
53
|
# @param params [Hash] Additional parameters on creation
|
57
54
|
# @option params [String,MXID] :user_id The user ID of the logged-in user
|
58
55
|
# @option params [Integer] :sync_filter_limit (20) Limit of timeline entries in syncs
|
59
|
-
# @see
|
56
|
+
# @see ActiveMatrix::Api.new for additional usable params
|
60
57
|
def initialize(hs_url, client_cache: :all, **params)
|
61
58
|
event_initialize
|
62
59
|
|
@@ -153,7 +150,7 @@ module MatrixSdk
|
|
153
150
|
|
154
151
|
break if data[:next_batch].nil?
|
155
152
|
|
156
|
-
since = data
|
153
|
+
since = data[:next_batch]
|
157
154
|
end
|
158
155
|
|
159
156
|
rooms
|
@@ -168,9 +165,9 @@ module MatrixSdk
|
|
168
165
|
|
169
166
|
# Retrieve an account data helper
|
170
167
|
def account_data
|
171
|
-
return
|
168
|
+
return ActiveMatrix::Util::AccountDataCache.new self if cache == :none
|
172
169
|
|
173
|
-
@account_data ||=
|
170
|
+
@account_data ||= ActiveMatrix::Util::AccountDataCache.new self
|
174
171
|
end
|
175
172
|
|
176
173
|
# Gets a direct message room for the given user if one exists
|
@@ -178,7 +175,7 @@ module MatrixSdk
|
|
178
175
|
# @note Will return the oldest room if multiple exist
|
179
176
|
# @return [Room,nil] A direct message room if one exists
|
180
177
|
def direct_room(mxid)
|
181
|
-
mxid =
|
178
|
+
mxid = ActiveMatrix::MXID.new mxid.to_s unless mxid.is_a? ActiveMatrix::MXID
|
182
179
|
raise ArgumentError, 'Must be a valid user ID' unless mxid.user?
|
183
180
|
|
184
181
|
room_id = direct_rooms[mxid.to_s]&.first
|
@@ -365,7 +362,7 @@ module MatrixSdk
|
|
365
362
|
end
|
366
363
|
|
367
364
|
def inspect
|
368
|
-
"#<
|
365
|
+
"#<ActiveMatrix::Response 3pid=#{to_s.inspect} added_at=\"#{added_at}\"#{" validated_at=\"#{validated_at}\"" if validated?}>"
|
369
366
|
end
|
370
367
|
end
|
371
368
|
end
|
@@ -376,13 +373,13 @@ module MatrixSdk
|
|
376
373
|
#
|
377
374
|
# @example Creating a room with an alias
|
378
375
|
# client.create_room('myroom')
|
379
|
-
# #<
|
376
|
+
# #<ActiveMatrix::Room ... >
|
380
377
|
#
|
381
378
|
# @param room_alias [String] A default alias to set on the room, should only be the localpart
|
382
379
|
# @return [Room] The resulting room
|
383
380
|
# @see Protocols::CS#create_room
|
384
381
|
def create_room(room_alias = nil, **params)
|
385
|
-
data = api.create_room(**params
|
382
|
+
data = api.create_room(**params, room_alias: room_alias)
|
386
383
|
ensure_room(data.room_id)
|
387
384
|
end
|
388
385
|
|
@@ -542,7 +539,7 @@ module MatrixSdk
|
|
542
539
|
attempts = 0
|
543
540
|
data = loop do
|
544
541
|
break api.sync(**extra_params)
|
545
|
-
rescue
|
542
|
+
rescue ActiveMatrix::MatrixTimeoutError => e
|
546
543
|
raise e if (attempts += 1) >= params.fetch(:allow_sync_retry, 0)
|
547
544
|
end
|
548
545
|
|
@@ -577,7 +574,7 @@ module MatrixSdk
|
|
577
574
|
orig_bad_sync_timeout = bad_sync_timeout + 0
|
578
575
|
while @should_listen
|
579
576
|
begin
|
580
|
-
sync(**params
|
577
|
+
sync(**params, timeout: timeout)
|
581
578
|
return unless @should_listen
|
582
579
|
|
583
580
|
bad_sync_timeout = orig_bad_sync_timeout
|
@@ -588,7 +585,7 @@ module MatrixSdk
|
|
588
585
|
logger.warn("A #{e.class} occurred during sync")
|
589
586
|
if e.httpstatus >= 500
|
590
587
|
logger.warn("Serverside error, retrying in #{bad_sync_timeout} seconds...")
|
591
|
-
sleep(bad_sync_timeout) if bad_sync_timeout.positive?
|
588
|
+
sleep(bad_sync_timeout) if bad_sync_timeout.positive?
|
592
589
|
bad_sync_timeout = [bad_sync_timeout * 2, @bad_sync_timeout_limit].min
|
593
590
|
end
|
594
591
|
end
|
@@ -620,10 +617,9 @@ module MatrixSdk
|
|
620
617
|
|
621
618
|
def handle_sync_response(data)
|
622
619
|
data.dig(:account_data, :events)&.each do |account_data|
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
end
|
620
|
+
# Store the account data in cache
|
621
|
+
self.account_data.write(account_data[:type], account_data[:content]) if account_data[:type]
|
622
|
+
|
627
623
|
fire_account_data(MatrixEvent.new(self, account_data))
|
628
624
|
end
|
629
625
|
|
@@ -679,16 +675,7 @@ module MatrixSdk
|
|
679
675
|
end
|
680
676
|
end
|
681
677
|
|
682
|
-
|
683
|
-
account_data.tinycache_adapter.cleanup if instance_variable_defined?(:@account_data) && @account_data
|
684
|
-
@rooms.each do |_id, room|
|
685
|
-
# Clean up old cache data after every sync
|
686
|
-
# TODO Run this in a thread?
|
687
|
-
room.tinycache_adapter.cleanup
|
688
|
-
room.account_data.tinycache_adapter.cleanup
|
689
|
-
room.room_state.tinycache_adapter.cleanup
|
690
|
-
end
|
691
|
-
end
|
678
|
+
# Rails.cache handles its own cleanup/expiration
|
692
679
|
|
693
680
|
nil
|
694
681
|
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
require 'concurrent'
|
5
|
+
|
6
|
+
module ActiveMatrix
|
7
|
+
# Manages a pool of Matrix client connections for efficiency
|
8
|
+
class ClientPool
|
9
|
+
include Singleton
|
10
|
+
include ActiveMatrix::Logging
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@pools = Concurrent::Hash.new
|
14
|
+
@config = ActiveMatrix.config
|
15
|
+
@mutex = Mutex.new
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get or create a client for a homeserver
|
19
|
+
def get_client(homeserver, **)
|
20
|
+
@mutex.synchronize do
|
21
|
+
pool = get_or_create_pool(homeserver)
|
22
|
+
pool.checkout(**)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return a client to the pool
|
27
|
+
def checkin(client)
|
28
|
+
homeserver = client.homeserver
|
29
|
+
pool = @pools[homeserver]
|
30
|
+
pool&.checkin(client)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get pool statistics
|
34
|
+
def stats
|
35
|
+
@pools.map do |homeserver, pool|
|
36
|
+
{
|
37
|
+
homeserver: homeserver,
|
38
|
+
size: pool.size,
|
39
|
+
available: pool.available_count,
|
40
|
+
in_use: pool.in_use_count
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Clear all pools
|
46
|
+
def clear!
|
47
|
+
@mutex.synchronize do
|
48
|
+
@pools.each_value(&:clear!)
|
49
|
+
@pools.clear
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Shutdown all pools
|
54
|
+
def shutdown
|
55
|
+
clear!
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def get_or_create_pool(homeserver)
|
61
|
+
@pools[homeserver] ||= HomeserverPool.new(
|
62
|
+
homeserver,
|
63
|
+
max_size: @config&.max_clients_per_homeserver || 5,
|
64
|
+
timeout: @config&.client_idle_timeout || 5.minutes
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Pool for a specific homeserver
|
69
|
+
class HomeserverPool
|
70
|
+
include ActiveMatrix::Logging
|
71
|
+
|
72
|
+
attr_reader :homeserver, :max_size, :timeout
|
73
|
+
|
74
|
+
def initialize(homeserver, max_size:, timeout:)
|
75
|
+
@homeserver = homeserver
|
76
|
+
@max_size = max_size
|
77
|
+
@timeout = timeout
|
78
|
+
@available = []
|
79
|
+
@in_use = {}
|
80
|
+
@mutex = Mutex.new
|
81
|
+
@condition = ConditionVariable.new
|
82
|
+
end
|
83
|
+
|
84
|
+
def checkout(**)
|
85
|
+
@mutex.synchronize do
|
86
|
+
# Try to find an available client
|
87
|
+
client = find_available_client
|
88
|
+
|
89
|
+
# Create new client if needed and pool not full
|
90
|
+
client = create_client(**) if client.nil? && @in_use.size < @max_size
|
91
|
+
|
92
|
+
# Wait for a client if pool is full
|
93
|
+
while client.nil?
|
94
|
+
@condition.wait(@mutex, 1)
|
95
|
+
client = find_available_client
|
96
|
+
end
|
97
|
+
|
98
|
+
# Mark as in use
|
99
|
+
@available.delete(client)
|
100
|
+
@in_use[client.object_id] = {
|
101
|
+
client: client,
|
102
|
+
checked_out_at: Time.current
|
103
|
+
}
|
104
|
+
|
105
|
+
client
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def checkin(client)
|
110
|
+
@mutex.synchronize do
|
111
|
+
entry = @in_use.delete(client.object_id)
|
112
|
+
return unless entry
|
113
|
+
|
114
|
+
# Add back to available pool if still valid
|
115
|
+
if client_valid?(client)
|
116
|
+
@available << client
|
117
|
+
else
|
118
|
+
# Client is no longer valid, don't return to pool
|
119
|
+
logger.debug "Discarding invalid client for #{@homeserver}"
|
120
|
+
end
|
121
|
+
|
122
|
+
# Signal waiting threads
|
123
|
+
@condition.signal
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def size
|
128
|
+
@mutex.synchronize { @available.size + @in_use.size }
|
129
|
+
end
|
130
|
+
|
131
|
+
def available_count
|
132
|
+
@mutex.synchronize { @available.size }
|
133
|
+
end
|
134
|
+
|
135
|
+
def in_use_count
|
136
|
+
@mutex.synchronize { @in_use.size }
|
137
|
+
end
|
138
|
+
|
139
|
+
def clear!
|
140
|
+
@mutex.synchronize do
|
141
|
+
# Stop all clients
|
142
|
+
(@available + @in_use.values.map { |e| e[:client] }).each do |client|
|
143
|
+
client.stop_listener_thread if client.listening?
|
144
|
+
client.logout if client.logged_in?
|
145
|
+
rescue StandardError => e
|
146
|
+
logger.error "Error cleaning up client: #{e.message}"
|
147
|
+
end
|
148
|
+
|
149
|
+
@available.clear
|
150
|
+
@in_use.clear
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def find_available_client
|
157
|
+
# Remove any expired clients
|
158
|
+
@available.select! { |client| client_valid?(client) }
|
159
|
+
|
160
|
+
# Return first available
|
161
|
+
@available.first
|
162
|
+
end
|
163
|
+
|
164
|
+
def create_client(**)
|
165
|
+
logger.debug "Creating new client for #{@homeserver}"
|
166
|
+
|
167
|
+
ActiveMatrix::Client.new(
|
168
|
+
@homeserver,
|
169
|
+
client_cache: :some,
|
170
|
+
sync_filter_limit: 20,
|
171
|
+
**
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
175
|
+
def client_valid?(client)
|
176
|
+
# Check if client is still connected and responsive
|
177
|
+
return false unless client
|
178
|
+
|
179
|
+
# Could add more validation here
|
180
|
+
true
|
181
|
+
rescue StandardError
|
182
|
+
false
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Monkey patch Client to support pooling
|
188
|
+
class Client
|
189
|
+
# Checkin this client back to the pool
|
190
|
+
def checkin_to_pool
|
191
|
+
ClientPool.instance.checkin(self) if defined?(ClientPool)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module
|
4
|
-
# A generic error raised for issues in the
|
3
|
+
module ActiveMatrix
|
4
|
+
# A generic error raised for issues in the ActiveMatrix
|
5
5
|
class MatrixError < StandardError
|
6
6
|
end
|
7
7
|
|
@@ -30,9 +30,9 @@ module MatrixSdk
|
|
30
30
|
@code = error[:errcode]
|
31
31
|
@httpstatus = status
|
32
32
|
@message = error[:error]
|
33
|
-
@data = error.
|
33
|
+
@data = error.except(:errcode, :error)
|
34
34
|
|
35
|
-
super
|
35
|
+
super(error[:error])
|
36
36
|
end
|
37
37
|
|
38
38
|
def to_s
|