mayu 0.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,168 @@
1
+ require 'mayu/association'
2
+ require 'mayu/user'
3
+ require 'mayu/device'
4
+ require 'mayu/map'
5
+ require 'mayu/ap'
6
+
7
+ require 'mayu/user_completer'
8
+
9
+ module Mayu
10
+ class Loader
11
+ def initialize(store:, user_completer: nil)
12
+ @store = store
13
+ @cached_user_completer = user_completer
14
+ end
15
+
16
+ attr_reader :store
17
+
18
+ def load
19
+ objects
20
+ self
21
+ end
22
+
23
+ def objects
24
+ @objects ||= store.get
25
+ end
26
+
27
+ ###
28
+
29
+ def associations
30
+ @associations ||= objects.fetch(:associations).map do |_|
31
+ Association.load(_).tap do |association|
32
+ association._ap_finder = method(:find_ap)
33
+ association._user_finder = method(:find_user)
34
+ association._device_finder = method(:find_device_by_mac)
35
+ association._ensure_relation_finders_satisfied!
36
+ end
37
+ end
38
+ end
39
+
40
+ def association_by_mac
41
+ @association_by_mac = associations.map do |_|
42
+ [_.mac, _]
43
+ end.to_h
44
+ end
45
+
46
+ def association_by_ip
47
+ @association_by_ip = associations.map do |_|
48
+ [_.ip, _]
49
+ end.to_h
50
+ end
51
+
52
+ def associations_by_ap
53
+ @association_by_ap = associations.group_by do |_|
54
+ _.ap_key.to_s
55
+ end.to_h
56
+ end
57
+
58
+ def find_association_by_mac(mac)
59
+ association_by_mac[mac]
60
+ end
61
+
62
+ def find_association_by_ip(ip)
63
+ association_by_ip[ip]
64
+ end
65
+
66
+ def find_associations_by_ap(k)
67
+ associations_by_ap.fetch(k.to_s, [])
68
+ end
69
+
70
+ ###
71
+
72
+ def users
73
+ @users ||= objects.fetch(:users).map do |_|
74
+ User.load(_).tap do |user|
75
+ user._devices_finder = method(:find_devices_by_user)
76
+ user._ensure_relation_finders_satisfied!
77
+ end
78
+ end
79
+ end
80
+
81
+ def user_by_key
82
+ @user_by_key ||= users.map do |_|
83
+ [_.key.to_sym, _]
84
+ end.to_h
85
+ end
86
+
87
+ def find_user(k)
88
+ user_by_key[k.to_sym]
89
+ end
90
+
91
+ def user_completer
92
+ @user_completer ||= @cached_user_completer ? @cached_user_completer.update(users) : UserCompleter.new(users)
93
+ end
94
+
95
+ def suggest_users(query)
96
+ user_completer.query(query)
97
+ end
98
+
99
+ ###
100
+
101
+ def devices
102
+ @devices ||= objects.fetch(:devices).map do |_|
103
+ Device.load(_).tap do |device|
104
+ device._user_finder = method(:find_user)
105
+ device._association_finder = proc { find_association_by_mac(device.mac) }
106
+ device._ensure_relation_finders_satisfied!
107
+ end
108
+ end
109
+ end
110
+
111
+ def device_by_mac
112
+ @device_by_mac ||= devices.reverse_each.map do |device|
113
+ [device.mac, device]
114
+ end.to_h
115
+ end
116
+
117
+ def devices_by_user
118
+ @devices_by_user ||= devices.reverse_each.group_by do |device|
119
+ device.user_key
120
+ end
121
+ end
122
+
123
+ def find_device_by_mac(k)
124
+ device_by_mac[k]
125
+ end
126
+
127
+ def find_devices_by_user(k)
128
+ devices_by_user.fetch(k, [])
129
+ end
130
+
131
+ ###
132
+
133
+ def maps
134
+ @maps ||= objects.fetch(:maps).map do |k, v|
135
+ [k, Map.load(v).tap do |map|
136
+ map.key = k
137
+ map._aps_finder = proc { find_aps_by_map(k).values }
138
+ map._ensure_relation_finders_satisfied!
139
+ end]
140
+ end.to_h
141
+ end
142
+
143
+ def find_map(k)
144
+ maps[k.to_sym]
145
+ end
146
+
147
+ ###
148
+
149
+ def aps
150
+ @aps ||= objects.fetch(:aps).map do |k,v|
151
+ [k, Ap.load(v).tap do |ap|
152
+ ap.key = k
153
+ ap._map_finder = method(:find_map)
154
+ ap._associations_finder = proc { find_associations_by_ap(k) }
155
+ ap._ensure_relation_finders_satisfied!
156
+ end]
157
+ end.to_h
158
+ end
159
+
160
+ def find_ap(k)
161
+ aps[k.to_sym]
162
+ end
163
+
164
+ def find_aps_by_map(key)
165
+ aps.select { |k,v| v.map_key.to_s == key.to_s }
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,42 @@
1
+ module Mayu
2
+ Map = Struct.new(:key, :name, :url, :fg_color, :bg_color, :highlight_color, keyword_init: true) do
3
+ include Mayu::Relation
4
+
5
+ def self.load(obj)
6
+ new(**obj)
7
+ end
8
+
9
+ relates :aps
10
+
11
+ def associations
12
+ @associations ||= aps.flat_map(&:associations)
13
+ end
14
+ def devices
15
+ @devices ||= aps.flat_map(&:devices)
16
+ end
17
+ def users
18
+ @users ||= aps.flat_map(&:users)
19
+ end
20
+
21
+ def associations_count
22
+ associations.size
23
+ end
24
+ def devices_count
25
+ devices.size
26
+ end
27
+ def users_count
28
+ users.size
29
+ end
30
+
31
+ def as_json
32
+ {
33
+ key: key,
34
+ name: name,
35
+ url: url,
36
+ fg_color: fg_color,
37
+ bg_color: bg_color,
38
+ highlight_color: highlight_color,
39
+ }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,55 @@
1
+ require 'mayu/loader'
2
+ require 'thread'
3
+
4
+ module Mayu
5
+ class PeriodicLoader
6
+ SHUTDOWN_KEY = :mayu_periodic_loader_shutdown
7
+ def initialize(interval:, **options)
8
+ @loaded_at = nil
9
+ @loader = nil
10
+ @interval = interval
11
+ @options = options
12
+ @lock = Mutex.new
13
+ @thread = nil
14
+ end
15
+
16
+ attr_reader :loaded_at, :loader
17
+
18
+ def start
19
+ return if @thread
20
+ @lock.synchronize do
21
+ return if @thread
22
+ reload
23
+ @thread = Thread.new(&method(:thread))
24
+ end
25
+ end
26
+
27
+ def shutdown
28
+ @lock.synchronize do
29
+ if @thread
30
+ @thread[SHUTDOWN_KEY] = true
31
+ end
32
+ @thread = nil
33
+ end
34
+ end
35
+
36
+ def reload
37
+ @loaded_at = Time.now
38
+ new_loader = Loader.new(user_completer: @loader&.user_completer, **@options).load
39
+ @loader = new_loader
40
+ end
41
+
42
+ def thread
43
+ loop do
44
+ return if @thread[SHUTDOWN_KEY]
45
+ @lock.synchronize do
46
+ reload
47
+ end
48
+ sleep @interval
49
+ rescue Exception => e
50
+ $stderr.puts e.full_message
51
+ sleep @interval
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ module Mayu
2
+ module Relation
3
+ def self.included(klass)
4
+ klass.extend ClassMethods
5
+ end
6
+
7
+ def _ensure_relation_finders_satisfied!
8
+ self.class.relations.map do |k|
9
+ unless instance_variable_get(:"@_#{k}_finder")
10
+ raise "#{k.class}: #{k.inspect} not satisfied"
11
+ end
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ def relations
17
+ @_relations ||= []
18
+ end
19
+ def relates(name)
20
+ self.relations << name
21
+
22
+ key_exist = self.instance_methods.include?(:"#{name}_key")
23
+ self.class_eval(<<-EOF, __FILE__, __LINE__ + 1)
24
+ def _#{name}_finder=(x)
25
+ @_#{name}_finder = x
26
+ end
27
+
28
+ def _#{name}
29
+ @_#{name}
30
+ end
31
+
32
+ def _#{name}=(x)
33
+ key = #{key_exist ? "#{name}_key" : 'nil'}
34
+ @_#{name}_key = key
35
+ @_#{name} = x
36
+ end
37
+
38
+ def #{name}
39
+ raise "#{name} finder not set" unless @_#{name}_finder
40
+ key = #{key_exist ? "#{name}_key" : 'nil'}
41
+ if @_#{name}_key && @_#{name}_key != key
42
+ @_#{name} = nil
43
+ end
44
+ self._#{name} ||= @_#{name}_finder[key]
45
+ end
46
+ EOF
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,43 @@
1
+ require 'json'
2
+
3
+ module Mayu
4
+
5
+ class Renderer
6
+ def initialize(spec)
7
+ @spec = spec
8
+ end
9
+
10
+ def render(obj, spec: @spec)
11
+ r = nil
12
+ case obj
13
+ when nil
14
+ return nil
15
+ when Array
16
+ return obj.map { |_| render(_, spec: spec) }
17
+ when Hash
18
+ r = obj
19
+ else
20
+ case
21
+ when obj.respond_to?(:as_json)
22
+ r = obj.as_json
23
+ when !spec.empty?
24
+ r = {}
25
+ else
26
+ return obj
27
+ end
28
+ end
29
+ next_specs, keys = [spec].flatten.partition { |_| Hash === _ }
30
+ aref = Hash === obj
31
+ r2 = r.transform_values do |v|
32
+ render(v, spec: {})
33
+ end
34
+ keys.each do |k|
35
+ r[k] = render(r.fetch(k) { aref ? obj.fetch(k) { obj.send(k) } : obj.send(k) }, spec: {})
36
+ end
37
+ (next_specs.inject(&:merge) || {}).each do |k, ns|
38
+ r[k] = render(r.fetch(k) { aref ? obj.fetch(k) { obj.send(k) } : obj.send(k) }, spec: ns)
39
+ end
40
+ r2.merge(r)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ require 'time'
2
+ module Mayu
3
+ module Stores
4
+ class Base
5
+ def initialize(**options)
6
+ @options = options
7
+ end
8
+
9
+ def put(obj)
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def get
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def for_json(obj)
18
+ case obj
19
+ when Array
20
+ obj.map{ |_| for_json(_) }
21
+ when Hash
22
+ obj.transform_values { |v| v.is_a?(Time) ? v.xmlschema : for_json(v) }
23
+ else
24
+ obj
25
+ end
26
+ end
27
+
28
+ def from_json(obj)
29
+ case obj
30
+ when Array
31
+ obj.map do |v|
32
+ from_json(v)
33
+ end
34
+ when Hash
35
+ obj.map do |k,v|
36
+ [k, k.to_s.end_with?('_at') ? (v && Time.xmlschema(v)) : from_json(v)]
37
+ end.to_h
38
+ else
39
+ obj
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,41 @@
1
+ require 'mayu/stores/base'
2
+
3
+ module Mayu
4
+ module Stores
5
+ class Concat < Base
6
+ def initialize(stores:)
7
+ @stores = stores
8
+ end
9
+
10
+ attr_reader :stores
11
+
12
+ def put(obj)
13
+ raise NotImplementedError, "this store is read only"
14
+ end
15
+
16
+ def get
17
+ objs = @stores.map(&:get)
18
+ keys = objs.flat_map(&:keys).uniq
19
+
20
+ r = {}
21
+ objs.each do |i|
22
+ keys.each do |k|
23
+ next unless i[k]
24
+ case r[k]
25
+ when Array
26
+ r[k] ||= []
27
+ r[k] += i[k]
28
+ when Hash
29
+ r[k] ||= {}
30
+ r[k] = r[k].merge(i[k])
31
+ else
32
+ r[k] = i[k]
33
+ end
34
+ end
35
+ end
36
+ r
37
+ end
38
+ end
39
+ end
40
+ end
41
+