activematrix 0.0.1 → 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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +218 -51
  3. data/lib/active_matrix/agent_manager.rb +275 -0
  4. data/lib/active_matrix/agent_registry.rb +154 -0
  5. data/lib/active_matrix/bot/multi_instance_base.rb +189 -0
  6. data/lib/active_matrix/client.rb +5 -15
  7. data/lib/active_matrix/client_pool.rb +194 -0
  8. data/lib/active_matrix/event_router.rb +215 -0
  9. data/lib/active_matrix/memory/agent_memory.rb +128 -0
  10. data/lib/active_matrix/memory/base.rb +101 -0
  11. data/lib/active_matrix/memory/conversation_memory.rb +161 -0
  12. data/lib/active_matrix/memory/global_memory.rb +153 -0
  13. data/lib/active_matrix/memory.rb +28 -0
  14. data/lib/active_matrix/room.rb +131 -51
  15. data/lib/active_matrix/rooms/space.rb +1 -5
  16. data/lib/active_matrix/user.rb +10 -0
  17. data/lib/active_matrix/util/account_data_cache.rb +62 -24
  18. data/lib/active_matrix/util/cacheable.rb +73 -0
  19. data/lib/active_matrix/util/extensions.rb +4 -0
  20. data/lib/active_matrix/util/state_event_cache.rb +106 -31
  21. data/lib/active_matrix/version.rb +1 -1
  22. data/lib/active_matrix.rb +51 -3
  23. data/lib/generators/active_matrix/bot/bot_generator.rb +38 -0
  24. data/lib/generators/active_matrix/bot/templates/bot.rb.erb +111 -0
  25. data/lib/generators/active_matrix/bot/templates/bot_spec.rb.erb +68 -0
  26. data/lib/generators/active_matrix/install/install_generator.rb +44 -0
  27. data/lib/generators/active_matrix/install/templates/README +30 -0
  28. data/lib/generators/active_matrix/install/templates/active_matrix.rb +33 -0
  29. data/lib/generators/active_matrix/install/templates/agent_memory.rb +47 -0
  30. data/lib/generators/active_matrix/install/templates/conversation_context.rb +72 -0
  31. data/lib/generators/active_matrix/install/templates/create_agent_memories.rb +17 -0
  32. data/lib/generators/active_matrix/install/templates/create_conversation_contexts.rb +21 -0
  33. data/lib/generators/active_matrix/install/templates/create_global_memories.rb +20 -0
  34. data/lib/generators/active_matrix/install/templates/create_matrix_agents.rb +26 -0
  35. data/lib/generators/active_matrix/install/templates/global_memory.rb +70 -0
  36. data/lib/generators/active_matrix/install/templates/matrix_agent.rb +127 -0
  37. metadata +110 -4
  38. data/lib/active_matrix/util/rails_cache_adapter.rb +0 -37
  39. data/lib/active_matrix/util/tinycache.rb +0 -145
  40. data/lib/active_matrix/util/tinycache_adapter.rb +0 -87
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+
5
+ module ActiveMatrix
6
+ module Memory
7
+ # Global memory storage accessible to all agents
8
+ class GlobalMemory < Base
9
+ include Singleton
10
+
11
+ # Get a value from global memory
12
+ def get(key)
13
+ fetch_with_cache(key) do
14
+ return nil unless defined?(::GlobalMemory)
15
+
16
+ ::GlobalMemory.get(key)
17
+ end
18
+ end
19
+
20
+ # Set a value in global memory
21
+ def set(key, value, category: nil, expires_in: nil, public_read: true, public_write: false)
22
+ return false unless defined?(::GlobalMemory)
23
+
24
+ write_through(key, value, expires_in: expires_in) do
25
+ ::GlobalMemory.set(key, value,
26
+ category: category,
27
+ expires_in: expires_in,
28
+ public_read: public_read,
29
+ public_write: public_write)
30
+ end
31
+ end
32
+
33
+ # Check if a key exists
34
+ def exists?(key)
35
+ return false unless defined?(::GlobalMemory)
36
+
37
+ if @cache_enabled && Rails.cache.exist?(cache_key(key))
38
+ true
39
+ else
40
+ ::GlobalMemory.active.exists?(key: key)
41
+ end
42
+ end
43
+
44
+ # Delete a key
45
+ def delete(key)
46
+ return false unless defined?(::GlobalMemory)
47
+
48
+ delete_through(key) do
49
+ ::GlobalMemory.where(key: key).destroy_all.any?
50
+ end
51
+ end
52
+
53
+ # Get all keys in a category
54
+ def keys(category: nil)
55
+ return [] unless defined?(::GlobalMemory)
56
+
57
+ scope = ::GlobalMemory.active
58
+ scope = scope.by_category(category) if category
59
+ scope.pluck(:key)
60
+ end
61
+
62
+ # Get all values in a category
63
+ def by_category(category)
64
+ return {} unless defined?(::GlobalMemory)
65
+
66
+ ::GlobalMemory.active.by_category(category).pluck(:key, :value).to_h
67
+ end
68
+
69
+ # Check if readable by agent
70
+ def readable?(key, agent = nil)
71
+ return false unless defined?(::GlobalMemory)
72
+
73
+ memory = ::GlobalMemory.find_by(key: key)
74
+ memory&.readable_by?(agent)
75
+ end
76
+
77
+ # Check if writable by agent
78
+ def writable?(key, agent = nil)
79
+ return false unless defined?(::GlobalMemory)
80
+
81
+ memory = ::GlobalMemory.find_by(key: key)
82
+ memory&.writable_by?(agent)
83
+ end
84
+
85
+ # Get with permission check
86
+ def get_for_agent(key, agent)
87
+ return nil unless readable?(key, agent)
88
+
89
+ get(key)
90
+ end
91
+
92
+ # Set with permission check
93
+ def set_for_agent(key, value, agent, **)
94
+ # Allow creating new keys or updating writable ones
95
+ memory = ::GlobalMemory.find_by(key: key)
96
+ return false if memory && !memory.writable_by?(agent)
97
+
98
+ set(key, value, **)
99
+ end
100
+
101
+ # Remember something globally
102
+ def remember(key, **)
103
+ value = get(key)
104
+ return value if value.present?
105
+
106
+ value = yield
107
+ set(key, value, **) if value.present?
108
+ value
109
+ end
110
+
111
+ # Broadcast a value to all agents
112
+ def broadcast(key, value, expires_in: 5.minutes)
113
+ set(key, value, category: 'broadcast', expires_in: expires_in, public_read: true)
114
+
115
+ # Notify all agents if event router is available
116
+ if defined?(EventRouter)
117
+ EventRouter.instance.broadcast_event({
118
+ type: 'global_memory.broadcast',
119
+ key: key,
120
+ value: value
121
+ })
122
+ end
123
+
124
+ true
125
+ end
126
+
127
+ # Share data between specific agents
128
+ def share(key, value, agent_names, expires_in: nil)
129
+ set(key, {
130
+ value: value,
131
+ allowed_agents: agent_names
132
+ }, category: 'shared', expires_in: expires_in, public_read: false)
133
+ end
134
+
135
+ # Get shared data if allowed
136
+ def get_shared(key, agent)
137
+ data = get(key)
138
+ return nil unless data.is_a?(Hash) && data['allowed_agents']
139
+
140
+ allowed = data['allowed_agents']
141
+ return unless allowed.include?(agent.name) || allowed.include?('*')
142
+
143
+ data['value']
144
+ end
145
+
146
+ protected
147
+
148
+ def cache_key(key)
149
+ "global/#{key}"
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveMatrix
4
+ # Memory system for multi-agent architecture
5
+ module Memory
6
+ autoload :AgentMemory, 'active_matrix/memory/agent_memory'
7
+ autoload :ConversationMemory, 'active_matrix/memory/conversation_memory'
8
+ autoload :GlobalMemory, 'active_matrix/memory/global_memory'
9
+ autoload :Base, 'active_matrix/memory/base'
10
+
11
+ class << self
12
+ # Get memory interface for an agent
13
+ def for_agent(agent)
14
+ AgentMemory.new(agent)
15
+ end
16
+
17
+ # Get conversation memory for agent and user
18
+ def for_conversation(agent, user_id, room_id)
19
+ ConversationMemory.new(agent, user_id, room_id)
20
+ end
21
+
22
+ # Access global memory
23
+ def global
24
+ GlobalMemory.instance
25
+ end
26
+ end
27
+ end
28
+ end
@@ -4,8 +4,8 @@ module ActiveMatrix
4
4
  # A class for tracking the information about a room on Matrix
5
5
  class Room
6
6
  extend ActiveMatrix::Extensions
7
- extend ActiveMatrix::Util::Tinycache
8
7
  include ActiveMatrix::Logging
8
+ include ActiveMatrix::Util::Cacheable
9
9
 
10
10
  # @!attribute [rw] event_history_limit
11
11
  # @return [Fixnum] the limit of events to keep in the event log
@@ -24,17 +24,9 @@ module ActiveMatrix
24
24
  # An inspect method that skips a handful of instance variables to avoid
25
25
  # flooding the terminal with debug data.
26
26
  # @return [String] a regular inspect string without the data for some variables
27
- ignore_inspect :client, :events, :prev_batch, :logger, :tinycache_adapter
28
-
29
- # Requires heavy lookups, so they're cached for an hour
30
- cached :joined_members, cache_level: :all, expires_in: 60 * 60
31
-
32
- # Only cache unfiltered requests for aliases and members
33
- cached :aliases, unless: proc { |_, args| args.any? }, cache_level: :all, expires_in: 60 * 60
34
- cached :all_members, unless: proc { |_, args| args.any? }, cache_level: :all, expires_in: 60 * 60
27
+ ignore_inspect :client, :events, :prev_batch, :logger
35
28
 
36
29
  alias room_id id
37
- alias members joined_members
38
30
 
39
31
  # Create a new room instance
40
32
  #
@@ -74,23 +66,22 @@ module ActiveMatrix
74
66
  @events = []
75
67
  @event_history_limit = 10
76
68
  @room_type = nil
69
+ @pre_populated_members = nil # For pre-populated members from tests
70
+ @cached_joined_members = nil # Instance cache for joined members
71
+ @cached_all_members = nil # Instance cache for all members
77
72
 
78
73
  @prev_batch = nil
79
74
 
80
75
  %i[name topic canonical_alias avatar_url].each do |type|
81
- room_state.tinycache_adapter.write("m.room.#{type}", { type => data.delete(type) }) if data.key? type
76
+ room_state.write("m.room.#{type}", { type => data.delete(type) }) if data.key? type
82
77
  end
83
- room_state.tinycache_adapter.write('m.room.join_rules', { join_rule: data.delete(:join_rule) }) if data.key? :join_rule
84
- room_state.tinycache_adapter.write('m.room.history_visibility', { history_visibility: data.delete(:world_readable) ? :world_readable : nil }) if data.key? :world_readable
78
+ room_state.write('m.room.join_rules', { join_rule: data.delete(:join_rule) }) if data.key? :join_rule
79
+ room_state.write('m.room.history_visibility', { history_visibility: data.delete(:world_readable) ? :world_readable : nil }) if data.key? :world_readable
85
80
 
86
81
  data.each do |k, v|
87
82
  next if %i[client].include? k
88
83
 
89
- if respond_to?(:"#{k}_cached?") && send(:"#{k}_cached?")
90
- tinycache_adapter.write(k, v)
91
- elsif instance_variable_defined? "@#{k}"
92
- instance_variable_set("@#{k}", v)
93
- end
84
+ instance_variable_set("@#{k}", v) if instance_variable_defined? "@#{k}"
94
85
  end
95
86
 
96
87
  @id = room_id.to_s
@@ -182,6 +173,36 @@ module ActiveMatrix
182
173
  #
183
174
  # @return [Array(User)] The list of members in the room
184
175
  def joined_members
176
+ # Return pre-populated members if they exist (for testing)
177
+ return @pre_populated_members if @pre_populated_members
178
+
179
+ # Return cached instance if available
180
+ return @cached_joined_members if @cached_joined_members
181
+
182
+ return fetch_joined_members unless cache_available?
183
+
184
+ # Cache the raw data that can be used to reconstruct User objects
185
+ members_data = cache.fetch(cache_key(:joined_members), expires_in: 1.hour) do
186
+ # Convert API response to cacheable format
187
+ api_response = client.api.get_room_joined_members(id)[:joined]
188
+ api_response.map do |mxid, data|
189
+ {
190
+ mxid: mxid.to_s,
191
+ display_name: data[:display_name],
192
+ avatar_url: data[:avatar_url]
193
+ }
194
+ end
195
+ end
196
+
197
+ # Reconstruct User objects from cached data and cache at instance level
198
+ @cached_joined_members = members_data.map do |member|
199
+ User.new(client, member[:mxid],
200
+ display_name: member[:display_name],
201
+ avatar_url: member[:avatar_url])
202
+ end
203
+ end
204
+
205
+ def fetch_joined_members
185
206
  client.api.get_room_joined_members(id)[:joined].map do |mxid, data|
186
207
  User.new(client, mxid.to_s,
187
208
  display_name: data.fetch(:display_name, nil),
@@ -189,6 +210,8 @@ module ActiveMatrix
189
210
  end
190
211
  end
191
212
 
213
+ alias members joined_members
214
+
192
215
  # Get all members (member events) in the room
193
216
  #
194
217
  # @note This will also count members who've knocked, been invited, have left, or have been banned.
@@ -197,6 +220,24 @@ module ActiveMatrix
197
220
  #
198
221
  # @return [Array(User)] The complete list of members in the room, regardless of membership state
199
222
  def all_members(**params)
223
+ # Return pre-populated members if they exist and no filtering params (for testing)
224
+ return @pre_populated_members if @pre_populated_members && params.empty?
225
+
226
+ # Return cached instance if available and no params
227
+ return @cached_all_members if @cached_all_members && params.empty?
228
+
229
+ return fetch_all_members(**params) if !params.empty? || client.cache == :none || !cache_available?
230
+
231
+ # Cache the raw member state keys, not User objects
232
+ members_data = cache.fetch(cache_key(:all_members), expires_in: 1.hour) do
233
+ client.api.get_room_members(id, **params)[:chunk].map { |ch| ch[:state_key] }
234
+ end
235
+
236
+ # Reconstruct User objects from cached data and cache at instance level
237
+ @cached_all_members = members_data.map { |state_key| client.get_user(state_key) }
238
+ end
239
+
240
+ def fetch_all_members(**params)
200
241
  client.api.get_room_members(id, **params)[:chunk].map { |ch| client.get_user(ch[:state_key]) }
201
242
  end
202
243
 
@@ -206,7 +247,8 @@ module ActiveMatrix
206
247
  #
207
248
  # @return [String,nil] The room name - if any
208
249
  def name
209
- get_state('m.room.name')[:name]
250
+ state = get_state('m.room.name')
251
+ state&.dig(:name)
210
252
  rescue MatrixNotFoundError
211
253
  # No room name has been specified
212
254
  nil
@@ -323,8 +365,20 @@ module ActiveMatrix
323
365
  # @param canonical_only [Boolean] Should the list of aliases only contain the canonical ones
324
366
  # @return [Array[String]] The assigned room aliases
325
367
  def aliases(canonical_only: true)
368
+ return fetch_aliases(canonical_only: canonical_only) if !canonical_only || client.cache == :none || !cache_available?
369
+
370
+ cache.fetch(cache_key(:aliases), expires_in: 1.hour) do
371
+ fetch_aliases(canonical_only: true)
372
+ end
373
+ end
374
+
375
+ def fetch_aliases(canonical_only: true)
326
376
  canonical = get_state('m.room.canonical_alias') rescue {}
327
- aliases = ([canonical[:alias]].compact + (canonical[:alt_aliases] || [])).uniq.sort
377
+ # Handle both hash-like and Response objects
378
+ alias_value = canonical.respond_to?(:alias) ? canonical.alias : canonical[:alias]
379
+ alt_aliases = canonical.respond_to?(:alt_aliases) ? canonical.alt_aliases : canonical[:alt_aliases]
380
+
381
+ aliases = ([alias_value].compact + (alt_aliases || [])).uniq.sort
328
382
  return aliases if canonical_only
329
383
 
330
384
  (aliases + client.api.get_room_aliases(id).aliases).uniq.sort
@@ -735,7 +789,8 @@ module ActiveMatrix
735
789
  # @return [Boolean] if the addition was successful or not
736
790
  def add_alias(room_alias)
737
791
  client.api.set_room_alias(id, room_alias)
738
- tinycache_adapter.read(:aliases) << room_alias if tinycache_adapter.exist?(:aliases)
792
+ # Clear the cache to force refresh
793
+ cache.delete(cache_key(:aliases)) if cache_available?
739
794
  true
740
795
  end
741
796
 
@@ -746,7 +801,7 @@ module ActiveMatrix
746
801
  # alias list updates.
747
802
  def reload_aliases!
748
803
  room_state.expire('m.room.canonical_alias')
749
- clear_aliases_cache
804
+ cache.delete(cache_key(:aliases)) if cache_available?
750
805
  end
751
806
  alias refresh_aliases! reload_aliases!
752
807
 
@@ -896,7 +951,7 @@ module ActiveMatrix
896
951
  def modify_user_power_levels(users = nil, users_default = nil)
897
952
  return false if users.nil? && users_default.nil?
898
953
 
899
- room_state.tinycache_adapter.expire 'm.room.power_levels'
954
+ room_state.expire 'm.room.power_levels'
900
955
 
901
956
  data = power_levels
902
957
  data[:users_default] = users_default unless users_default.nil?
@@ -928,7 +983,7 @@ module ActiveMatrix
928
983
  def modify_required_power_levels(events = nil, params = {})
929
984
  return false if events.nil? && (params.nil? || params.empty?)
930
985
 
931
- room_state.tinycache_adapter.expire 'm.room.power_levels'
986
+ room_state.expire 'm.room.power_levels'
932
987
 
933
988
  data = power_levels
934
989
  data.merge!(params)
@@ -946,38 +1001,52 @@ module ActiveMatrix
946
1001
 
947
1002
  private
948
1003
 
1004
+ def cache_key(method_name)
1005
+ "activematrix:room:#{id}:#{method_name}"
1006
+ end
1007
+
1008
+ def cache_available?
1009
+ defined?(::Rails) && ::Rails.respond_to?(:cache) && ::Rails.cache
1010
+ end
1011
+
1012
+ def cache
1013
+ ::Rails.cache
1014
+ end
1015
+
949
1016
  def ensure_member(member)
950
1017
  return unless client.cache == :all
951
1018
 
952
- tinycache_adapter.write(:joined_members, []) unless tinycache_adapter.exist? :joined_members
953
-
954
- members = tinycache_adapter.read(:joined_members) || []
955
- members << member unless members.any? { |m| m.id == member.id }
1019
+ # Add member to pre-populated list
1020
+ @pre_populated_members ||= []
1021
+ @pre_populated_members << member unless @pre_populated_members.include?(member)
956
1022
 
957
- tinycache_adapter.write(:joined_members, members)
1023
+ # Clear the cache to force a refresh on next access
1024
+ cache.delete(cache_key(:joined_members)) if cache_available?
958
1025
  end
959
1026
 
960
1027
  def handle_room_member(event)
961
1028
  return unless client.cache == :all
962
1029
 
963
- if event.dig(*%i[content membership]) == 'join'
964
- ensure_member(client.get_user(event[:state_key]).dup.tap do |u|
965
- u.instance_variable_set(:@display_name, event.dig(*%i[content displayname]))
966
- end)
967
- elsif tinycache_adapter.exist? :joined_members
968
- members = tinycache_adapter.read(:joined_members)
969
- members.delete_if { |m| m.id == event[:state_key] }
970
- end
1030
+ # Cache the user if it's a join event
1031
+ client.get_user(event[:state_key]) if event.dig(:content, :membership) == 'join' && event[:state_key]
1032
+
1033
+ # Clear pre-populated members when membership changes
1034
+ @pre_populated_members = nil
1035
+
1036
+ # Clear instance caches when membership changes
1037
+ @cached_joined_members = nil
1038
+ @cached_all_members = nil
1039
+
1040
+ # Clear the cache when membership changes
1041
+ cache.delete(cache_key(:joined_members)) if cache_available?
1042
+ cache.delete(cache_key(:all_members)) if cache_available?
971
1043
  end
972
1044
 
973
1045
  def handle_room_canonical_alias(event)
974
- room_state.tinycache_adapter.write('m.room.canonical_alias', event[:content], expires_in: room_state.cache_time)
975
- canonical_alias = event.dig(*%i[content alias])
1046
+ room_state.write('m.room.canonical_alias', event[:content])
976
1047
 
977
- data = tinycache_adapter.read(:aliases) || []
978
- data << canonical_alias
979
- data += event.dig(*%i[content alt_aliases]) || []
980
- tinycache_adapter.write(:aliases, data.uniq.sort)
1048
+ # Clear the aliases cache
1049
+ cache.delete(cache_key(:aliases)) if cache_available?
981
1050
  end
982
1051
 
983
1052
  def room_handlers?
@@ -1001,10 +1070,8 @@ module ActiveMatrix
1001
1070
  end
1002
1071
 
1003
1072
  def put_account_data(event)
1004
- if client.cache != :none
1005
- adapter = account_data.tinycache_adapter
1006
- adapter.write(event[:type], event[:content], expires_in: account_data.cache_time)
1007
- end
1073
+ # Store the account data in cache
1074
+ account_data.write(event[:type], event[:content]) if event[:type]
1008
1075
 
1009
1076
  return unless room_handlers?
1010
1077
 
@@ -1026,15 +1093,28 @@ module ActiveMatrix
1026
1093
  if INTERNAL_HANDLERS.key? event[:type]
1027
1094
  send(INTERNAL_HANDLERS[event[:type]], event)
1028
1095
  elsif client.cache != :none
1029
- adapter = room_state.tinycache_adapter
1030
- key = event[:type]
1031
- key += "|#{event[:state_key]}" unless event[:state_key].nil? || event[:state_key].empty?
1032
- adapter.write(key, event[:content], expires_in: room_state.cache_time)
1096
+ # StateEventCache will handle caching internally
1097
+ room_state.write(event[:type], event[:content], event[:state_key])
1033
1098
  end
1034
1099
 
1035
1100
  return unless room_handlers?
1036
1101
 
1037
1102
  ensure_room_handlers[:state_event].fire(MatrixEvent.new(self, event), event[:type])
1038
1103
  end
1104
+
1105
+ # Define what attributes to cache
1106
+ def cache_attributes
1107
+ {
1108
+ id: @id,
1109
+ room_type: @room_type,
1110
+ prev_batch: @prev_batch,
1111
+ event_history_limit: @event_history_limit
1112
+ }
1113
+ end
1114
+
1115
+ # Override cache_id to use room ID
1116
+ def cache_id
1117
+ @id
1118
+ end
1039
1119
  end
1040
1120
  end
@@ -26,11 +26,7 @@ module ActiveMatrix::Rooms
26
26
 
27
27
  # Inject available room information
28
28
  r.each do |k, v|
29
- if room.respond_to?(:"#{k}_cached?") && send(:"#{k}_cached?")
30
- room.send(:tinycache_adapter).write(k, v)
31
- elsif room.instance_variable_defined? "@#{k}"
32
- room.instance_variable_set("@#{k}", v)
33
- end
29
+ room.instance_variable_set("@#{k}", v) if room.instance_variable_defined? "@#{k}"
34
30
  end
35
31
  room
36
32
  end
@@ -4,6 +4,7 @@ module ActiveMatrix
4
4
  # A class for tracking information about a user on Matrix
5
5
  class User
6
6
  extend ActiveMatrix::Extensions
7
+ include ActiveMatrix::Util::Cacheable
7
8
 
8
9
  attr_reader :id, :client
9
10
  alias user_id :id
@@ -157,6 +158,15 @@ module ActiveMatrix
157
158
  end
158
159
  end
159
160
 
161
+ # Define what attributes to cache
162
+ def cache_attributes
163
+ {
164
+ id: @id,
165
+ display_name: @display_name,
166
+ avatar_url: @avatar_url
167
+ }
168
+ end
169
+
160
170
  private
161
171
 
162
172
  def raw_presence
@@ -3,20 +3,20 @@
3
3
  module ActiveMatrix::Util
4
4
  class AccountDataCache
5
5
  extend ActiveMatrix::Extensions
6
- extend ActiveMatrix::Util::Tinycache
7
6
  include Enumerable
8
7
 
9
8
  attr_reader :client, :room
10
9
 
11
10
  attr_accessor :cache_time
12
11
 
13
- ignore_inspect :client, :room, :tinycache_adapter
12
+ ignore_inspect :client, :room
14
13
 
15
14
  def initialize(client, room: nil, cache_time: 1 * 60 * 60, **_params)
16
15
  raise ArgumentError, 'Must be given a Client instance' unless client.is_a? ActiveMatrix::Client
17
16
 
18
17
  @client = client
19
18
  @cache_time = cache_time
19
+ @tracked_keys = Set.new
20
20
 
21
21
  return unless room
22
22
 
@@ -25,15 +25,22 @@ module ActiveMatrix::Util
25
25
  end
26
26
 
27
27
  def reload!
28
- tinycache_adapter.clear
28
+ # Clear all cache entries for this account data
29
+ return unless cache_available?
30
+
31
+ if room
32
+ cache.delete_matched("activematrix:account_data:#{client.mxid}:room:#{room.id}:*")
33
+ else
34
+ cache.delete_matched("activematrix:account_data:#{client.mxid}:global:*")
35
+ end
29
36
  end
30
37
 
31
38
  def keys
32
- tinycache_adapter.send(:cache).keys
39
+ @tracked_keys.to_a.sort
33
40
  end
34
41
 
35
42
  def values
36
- keys.map { |key| tinycache_adapter.read(key) }
43
+ []
37
44
  end
38
45
 
39
46
  def size
@@ -41,18 +48,12 @@ module ActiveMatrix::Util
41
48
  end
42
49
 
43
50
  def key?(key)
44
- keys.key?(key.to_s)
51
+ cache_available? && cache.exist?(cache_key(key))
45
52
  end
46
53
 
47
54
  def each(live: false)
48
- return to_enum(__method__, live: live) { keys.count } unless block_given?
49
-
50
- keys.each do |key|
51
- v = live ? self[key] : tinycache_adapter.read(key)
52
- # hash = v.hash
53
- yield key, v
54
- # self[key] = v if hash != v.hash
55
- end
55
+ to_enum(__method__, live: live) { 0 } unless block_given?
56
+ # Not enumerable with Rails.cache
56
57
  end
57
58
 
58
59
  def delete(key)
@@ -62,20 +63,30 @@ module ActiveMatrix::Util
62
63
  else
63
64
  client.api.set_account_data(client.mxid, key, {})
64
65
  end
65
- tinycache_adapter.delete(key)
66
+ cache.delete(cache_key(key)) if cache_available?
66
67
  end
67
68
 
68
69
  def [](key)
69
70
  key = key.to_s unless key.is_a? String
70
- tinycache_adapter.fetch(key, expires_in: @cache_time) do
71
- if room
72
- client.api.get_room_account_data(client.mxid, room.id, key)
73
- else
74
- client.api.get_account_data(client.mxid, key)
75
- end
76
- rescue ActiveMatrix::MatrixNotFoundError
77
- {}
71
+
72
+ # Track the key whenever it's accessed
73
+ @tracked_keys.add(key)
74
+
75
+ return fetch_account_data(key) unless cache_available?
76
+
77
+ cache.fetch(cache_key(key), expires_in: @cache_time) do
78
+ fetch_account_data(key)
79
+ end
80
+ end
81
+
82
+ def fetch_account_data(key)
83
+ if room
84
+ client.api.get_room_account_data(client.mxid, room.id, key)
85
+ else
86
+ client.api.get_account_data(client.mxid, key)
78
87
  end
88
+ rescue ActiveMatrix::MatrixNotFoundError
89
+ {}
79
90
  end
80
91
 
81
92
  def []=(key, value)
@@ -85,7 +96,34 @@ module ActiveMatrix::Util
85
96
  else
86
97
  client.api.set_account_data(client.mxid, key, value)
87
98
  end
88
- tinycache_adapter.write(key, value)
99
+
100
+ @tracked_keys.add(key)
101
+ cache.write(cache_key(key), value, expires_in: @cache_time) if cache_available?
102
+ end
103
+
104
+ # Write data without making API call (for sync responses)
105
+ def write(key, value)
106
+ key = key.to_s unless key.is_a? String
107
+ @tracked_keys.add(key)
108
+ cache.write(cache_key(key), value, expires_in: @cache_time) if cache_available?
109
+ end
110
+
111
+ private
112
+
113
+ def cache_key(key)
114
+ if room
115
+ "activematrix:account_data:#{client.mxid}:room:#{room.id}:#{key}"
116
+ else
117
+ "activematrix:account_data:#{client.mxid}:global:#{key}"
118
+ end
119
+ end
120
+
121
+ def cache_available?
122
+ defined?(::Rails) && ::Rails.respond_to?(:cache) && ::Rails.cache
123
+ end
124
+
125
+ def cache
126
+ ::Rails.cache
89
127
  end
90
128
  end
91
129
  end