matrix_sdk 2.1.2 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MatrixSdk::Rooms
4
+ class Space < MatrixSdk::Room
5
+ TYPE = 'm.space'
6
+
7
+ def tree(suggested_only: nil, max_rooms: nil)
8
+ begin
9
+ data = client.api.request :get, :client_unstable, "/org.matrix.msc2946/rooms/#{id}/spaces", query: {
10
+ suggested_only: suggested_only,
11
+ max_rooms_per_space: max_rooms
12
+ }.compact
13
+ rescue
14
+ data = client.api.request :get, :client_r0, "/rooms/#{id}/spaces", query: {
15
+ suggested_only: suggested_only,
16
+ max_rooms_per_space: max_rooms
17
+ }.compact
18
+ end
19
+
20
+ rooms = data.rooms.map do |r|
21
+ next if r[:room_id] == id
22
+
23
+ room = client.ensure_room(r[:room_id])
24
+ room.instance_variable_set :@room_type, r[:room_type] if r.key? :room_type
25
+ room = room.to_space if room.space?
26
+
27
+ # Inject available room information
28
+ r.each do |k, v|
29
+ if room.respond_to?("#{k}_cached?".to_sym) && send("#{k}_cached?".to_sym)
30
+ room.send(:tinycache_adapter).write(k, v)
31
+ elsif room.instance_variable_defined? "@#{k}"
32
+ room.instance_variable_set("@#{k}", v)
33
+ end
34
+ end
35
+ room
36
+ end
37
+ rooms.compact!
38
+
39
+ grouping = {}
40
+ data.events.each do |ev|
41
+ next unless ev[:type] == 'm.space.child'
42
+ next unless ev[:content].key? :via
43
+
44
+ d = (grouping[ev[:room_id]] ||= [])
45
+ d << ev[:state_key]
46
+ end
47
+
48
+ build_tree = proc do |entry|
49
+ next if entry.nil?
50
+
51
+ room = self if entry == id
52
+ room ||= rooms.find { |r| r.id == entry }
53
+ puts "Unable to find room for entry #{entry}" unless room
54
+ # next if room.nil?
55
+
56
+ ret = {
57
+ room => []
58
+ }
59
+
60
+ grouping[entry]&.each do |child|
61
+ if grouping.key?(child)
62
+ ret[room] << build_tree.call(child)
63
+ else
64
+ child_r = self if child == id
65
+ child_r ||= rooms.find { |r| r.id == child }
66
+
67
+ ret[room] << child_r
68
+ end
69
+ end
70
+
71
+ ret[room].compact!
72
+
73
+ ret
74
+ end
75
+
76
+ build_tree.call(id)
77
+ end
78
+ end
79
+ end
@@ -59,7 +59,7 @@ module MatrixSdk
59
59
  # Only works for the current user object, as requested by
60
60
  # client.get_user(:self)
61
61
  #
62
- # @param url [String,URI::MATRIX] the new avatar URL
62
+ # @param url [String,URI::MXC] the new avatar URL
63
63
  # @note Requires a mxc:// URL, check example on
64
64
  # {MatrixSdk::Protocols::CS#set_avatar_url} for how this can be done
65
65
  # @see MatrixSdk::Protocols::CS#set_avatar_url
@@ -122,6 +122,14 @@ module MatrixSdk
122
122
  Time.now - (since / 1000)
123
123
  end
124
124
 
125
+ # Gets a direct message room with the user if one exists
126
+ #
127
+ # @return [Room,nil] A direct message room if one exists
128
+ # @see MatrixSdk::Client#direct_room
129
+ def direct_room
130
+ client.direct_room(id)
131
+ end
132
+
125
133
  # Returns all the current device keys for the user, retrieving them if necessary
126
134
  def device_keys
127
135
  @device_keys ||= client.api.keys_query(device_keys: { id => [] }).yield_self do |resp|
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MatrixSdk
4
+ class EventHandlerArray < Hash
5
+ include MatrixSdk::Logging
6
+ attr_accessor :reraise_exceptions
7
+
8
+ def initialize(*args)
9
+ @reraise_exceptions = false
10
+
11
+ super(*args)
12
+ end
13
+
14
+ def add_handler(filter = nil, id = nil, &block)
15
+ id ||= block.hash
16
+ self[id] = { filter: filter, id: id, block: block }
17
+ end
18
+
19
+ def remove_handler(id)
20
+ delete id
21
+ end
22
+
23
+ def fire(event, filter = nil)
24
+ reverse_each do |_k, h|
25
+ h[:block].call(event) if !h[:filter] || event.matches?(h[:filter], filter)
26
+ rescue StandardError => e
27
+ logger.error "#{e.class.name} occurred when firing event (#{event})\n#{e}"
28
+
29
+ raise e if @reraise_exceptions
30
+ end
31
+ end
32
+ end
33
+
34
+ class Event
35
+ extend MatrixSdk::Extensions
36
+
37
+ attr_writer :handled
38
+
39
+ ignore_inspect :sender
40
+
41
+ def initialize(sender)
42
+ @sender = sender
43
+ @handled = false
44
+ end
45
+
46
+ def handled?
47
+ @handled
48
+ end
49
+
50
+ def matches?(_filter)
51
+ true
52
+ end
53
+ end
54
+
55
+ class ErrorEvent < Event
56
+ attr_accessor :error
57
+
58
+ def initialize(error, source)
59
+ @error = error
60
+ super source
61
+ end
62
+
63
+ def source
64
+ @sender
65
+ end
66
+ end
67
+
68
+ class MatrixEvent < Event
69
+ attr_accessor :event, :filter
70
+ alias data event
71
+
72
+ ignore_inspect :sender
73
+
74
+ def initialize(sender, event = nil, filter = nil)
75
+ @event = event
76
+ @filter = filter || @event[:type]
77
+ super sender
78
+ end
79
+
80
+ def matches?(filter, filter_override = nil)
81
+ return true if filter_override.nil? && (@filter.nil? || filter.nil?)
82
+
83
+ to_match = filter_override || @filter
84
+ if filter.is_a? Regexp
85
+ filter.match(to_match) { true } || false
86
+ else
87
+ to_match == filter
88
+ end
89
+ end
90
+
91
+ def [](key)
92
+ event[key]
93
+ end
94
+
95
+ def to_s
96
+ "#{event[:type]}: #{event.reject { |k, _v| k == :type }.to_json}"
97
+ end
98
+
99
+ def method_missing(method, *args)
100
+ return event[method] if event.key? method
101
+
102
+ super
103
+ end
104
+
105
+ def respond_to_missing?(method, *)
106
+ return true if event.key? method
107
+
108
+ super
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless Object.respond_to? :yield_self
4
+ class Object
5
+ def yield_self
6
+ yield(self)
7
+ end
8
+ end
9
+ end
10
+
11
+ module MatrixSdk
12
+ module Extensions
13
+ def events(*symbols)
14
+ module_name = "#{name}Events"
15
+
16
+ initializers = []
17
+ readers = []
18
+ methods = []
19
+
20
+ symbols.each do |sym|
21
+ name = sym.to_s
22
+
23
+ initializers << "
24
+ @on_#{name} = MatrixSdk::EventHandlerArray.new
25
+ "
26
+ readers << ":on_#{name}"
27
+ methods << "
28
+ def fire_#{name}(ev, filter = nil)
29
+ @on_#{name}.fire(ev, filter)
30
+ when_#{name}(ev) if !ev.handled?
31
+ end
32
+
33
+ def when_#{name}(ev); end
34
+ "
35
+ end
36
+
37
+ class_eval "
38
+ module #{module_name}
39
+ attr_reader #{readers.join ', '}
40
+
41
+ def event_initialize
42
+ #{initializers.join}
43
+ end
44
+
45
+ #{methods.join}
46
+ end
47
+
48
+ include #{module_name}
49
+ ", __FILE__, __LINE__ - 12
50
+ end
51
+
52
+ def ignore_inspect(*symbols)
53
+ class_eval %*
54
+ def inspect
55
+ reentrant = caller_locations.any? { |l| l.absolute_path == __FILE__ && l.label == 'inspect' }
56
+ "\\\#<\#{self.class} \#{instance_variables
57
+ .reject { |f| %i[#{symbols.map { |s| "@#{s}" }.join ' '}].include? f }
58
+ .map { |f| "\#{f}=\#{reentrant ? instance_variable_get(f) : instance_variable_get(f).inspect}" }.join " " }}>"
59
+ end
60
+ *, __FILE__, __LINE__ - 7
61
+ end
62
+ end
63
+
64
+ module Logging
65
+ def logger
66
+ return MatrixSdk.logger if MatrixSdk.global_logger?
67
+
68
+ @logger ||= ::Logging.logger[self]
69
+ end
70
+
71
+ def logger=(logger)
72
+ @logger = logger
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'matrix_sdk/util/tinycache_adapter'
4
+
5
+ module MatrixSdk::Util
6
+ module Tinycache
7
+ CACHE_LEVELS = {
8
+ none: 0,
9
+ some: 1,
10
+ all: 2
11
+ }.freeze
12
+
13
+ def self.adapter
14
+ @adapter ||= TinycacheAdapter
15
+ end
16
+
17
+ def self.adapter=(adapter)
18
+ @adapter = adapter
19
+ end
20
+
21
+ def self.extended(base)
22
+ helper_name = base.send(:cache_helper_module_name)
23
+ base.send :remove_const, helper_name if base.const_defined?(helper_name)
24
+ base.prepend base.const_set(helper_name, Module.new)
25
+
26
+ base.include InstanceMethods
27
+ end
28
+
29
+ def cached(*methods, **opts)
30
+ methods.each { |method| build_cache_methods(method, **opts) }
31
+ end
32
+
33
+ module InstanceMethods
34
+ def tinycache_adapter
35
+ @tinycache_adapter ||= Tinycache.adapter.new.tap do |adapter|
36
+ adapter.config = self.class.tinycache_adapter_config if adapter.respond_to? :config=
37
+ adapter.client = client if respond_to?(:client) && adapter.respond_to?(:client=)
38
+ end
39
+ end
40
+ end
41
+
42
+ def tinycache_adapter_config
43
+ @tinycache_adapter_config ||= {}
44
+ end
45
+
46
+ private
47
+
48
+ def default_cache_key
49
+ proc do |method_name, _method_args|
50
+ method_name.to_sym
51
+ end
52
+ end
53
+
54
+ def cache_helper_module_name
55
+ class_name = name&.gsub(/:/, '') || to_s.gsub(/[^a-zA-Z_0-9]/, '')
56
+ "#{class_name}Tinycache"
57
+ end
58
+
59
+ def build_cache_methods(method_name, cache_key: default_cache_key, cache_level: :none, expires_in: nil, **opts)
60
+ raise ArgumentError, 'Cache key must be a three-arg proc' unless cache_key.is_a? Proc
61
+
62
+ method_names = build_method_names(method_name)
63
+ tinycache_adapter_config[method_name] = {
64
+ level: cache_level,
65
+ expires: expires_in || 1 * 365 * 24 * 60 * 60 # 1 year
66
+ }
67
+
68
+ helper = const_get(cache_helper_module_name)
69
+ return if method_names.any? { |k, _| helper.respond_to? k }
70
+
71
+ helper.class_eval do
72
+ define_method(method_names[:cache_key]) do |*args|
73
+ cache_key.call(method_name, args)
74
+ end
75
+
76
+ define_method(method_names[:with_cache]) do |*args|
77
+ tinycache_adapter.fetch(__send__(method_names[:cache_key], *args), expires_in: expires_in) do
78
+ __send__(method_names[:without_cache], *args)
79
+ end
80
+ end
81
+
82
+ define_method(method_names[:without_cache]) do |*args|
83
+ orig = method(method_name).super_method
84
+ orig.call(*args)
85
+ end
86
+
87
+ define_method(method_names[:clear_cache]) do |*args|
88
+ tinycache_adapter.delete(__send__(method_names[:cache_key], *args))
89
+ end
90
+
91
+ define_method(method_names[:cached]) do
92
+ true
93
+ end
94
+
95
+ define_method(method_names[:has_value]) do |*args|
96
+ tinycache_adapter.valid?(__send__(method_names[:cache_key], *args))
97
+ end
98
+
99
+ define_method(method_name) do |*args|
100
+ unless_proc = opts[:unless].is_a?(Symbol) ? opts[:unless].to_proc : opts[:unless]
101
+
102
+ skip_cache = false
103
+ skip_cache ||= unless_proc&.call(self, method_name, args)
104
+ skip_cache ||= CACHE_LEVELS[client&.cache || :all] < CACHE_LEVELS[cache_level]
105
+
106
+ if skip_cache
107
+ __send__(method_names[:without_cache], *args)
108
+ else
109
+ __send__(method_names[:with_cache], *args)
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ def build_method_names(method)
116
+ # Clean up method name (split any suffix)
117
+ method_name = method.to_s.sub(/([?!=])$/, '')
118
+ punctuation = Regexp.last_match(-1)
119
+
120
+ {
121
+ cache_key: "#{method_name}_cache_key#{punctuation}",
122
+ with_cache: "#{method_name}_with_cache#{punctuation}",
123
+ without_cache: "#{method_name}_without_cache#{punctuation}",
124
+ clear_cache: "clear_#{method_name}_cache#{punctuation}",
125
+ cached: "#{method}_cached?",
126
+ has_value: "#{method}_has_value?"
127
+ }
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MatrixSdk::Util
4
+ class TinycacheAdapter
5
+ attr_accessor :config, :client
6
+
7
+ def initialize
8
+ @config = {}
9
+
10
+ clear
11
+ end
12
+
13
+ def read(key)
14
+ cache[key]&.value
15
+ end
16
+
17
+ def write(key, value, expires_in: nil, cache_level: nil)
18
+ expires_in ||= config.dig(key, :expires)
19
+ expires_in ||= 24 * 60 * 60
20
+ cache_level ||= client&.cache
21
+ cache_level ||= :all
22
+ cache_level = Tinycache::CACHE_LEVELS[cache_level] unless cache_level.is_a? Integer
23
+
24
+ return value if cache_level < Tinycache::CACHE_LEVELS[config.dig(key, :level) || :none]
25
+
26
+ cache[key] = Value.new(value, Time.now, Time.now + expires_in)
27
+ value
28
+ end
29
+
30
+ def exist?(key)
31
+ cache.key?(key)
32
+ end
33
+
34
+ def valid?(key)
35
+ exist?(key) && !cache[key].expired?
36
+ end
37
+
38
+ def fetch(key, expires_in: nil, cache_level: nil, **_opts)
39
+ expires_in ||= config.dig(key, :expires)
40
+ cache_level ||= client&.cache
41
+ cache_level ||= :all
42
+ cache_level = Tinycache::CACHE_LEVELS[cache_level]
43
+
44
+ return read(key) if exist?(key) && !cache[key].expired?
45
+
46
+ value = yield
47
+ write(key, value, expires_in: expires_in, cache_level: cache_level)
48
+ end
49
+
50
+ def delete(key)
51
+ return false unless exist?(key)
52
+
53
+ cache.delete key
54
+ true
55
+ end
56
+
57
+ def clear
58
+ @cache = {}
59
+ end
60
+
61
+ def cleanup
62
+ @cache.delete_if { |_, v| v.expired? }
63
+ end
64
+
65
+ private
66
+
67
+ Value = Struct.new(:value, :timestamp, :expires_at) do
68
+ def expired?
69
+ return false if expires_at.nil?
70
+
71
+ Time.now > expires_at
72
+ end
73
+ end
74
+
75
+ attr_reader :cache
76
+ end
77
+ end