meshchat 0.8.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +39 -11
- data/lib/meshchat.rb +48 -42
- data/lib/meshchat/configuration.rb +14 -0
- data/lib/meshchat/configuration/app_config.rb +63 -0
- data/lib/meshchat/configuration/database.rb +41 -0
- data/lib/meshchat/{config → configuration}/hash_file.rb +7 -6
- data/lib/meshchat/configuration/identity.rb +79 -0
- data/lib/meshchat/{config → configuration}/settings.rb +22 -26
- data/lib/meshchat/debug.rb +69 -0
- data/lib/meshchat/encryption.rb +7 -2
- data/lib/meshchat/encryption/aes_rsa.rb +2 -1
- data/lib/meshchat/encryption/passthrough.rb +5 -3
- data/lib/meshchat/locale/en.yml +14 -0
- data/lib/meshchat/models/node.rb +140 -0
- data/lib/meshchat/network.rb +19 -0
- data/lib/meshchat/network/dispatcher.rb +83 -0
- data/lib/meshchat/network/errors.rb +11 -0
- data/lib/meshchat/network/incoming.rb +13 -0
- data/lib/meshchat/network/incoming/message_decryptor.rb +51 -0
- data/lib/meshchat/network/incoming/message_processor.rb +75 -0
- data/lib/meshchat/network/incoming/request_processor.rb +30 -0
- data/lib/meshchat/network/local.rb +12 -0
- data/lib/meshchat/network/local/connection.rb +58 -0
- data/lib/meshchat/network/local/server.rb +69 -0
- data/lib/meshchat/network/message.rb +34 -0
- data/lib/meshchat/network/message/base.rb +139 -0
- data/lib/meshchat/network/message/chat.rb +9 -0
- data/lib/meshchat/network/message/disconnect.rb +21 -0
- data/lib/meshchat/network/message/emote.rb +9 -0
- data/lib/meshchat/network/message/factory.rb +80 -0
- data/lib/meshchat/network/message/node_list.rb +75 -0
- data/lib/meshchat/network/message/node_list_diff.rb +18 -0
- data/lib/meshchat/network/message/node_list_hash.rb +32 -0
- data/lib/meshchat/network/message/ping.rb +31 -0
- data/lib/meshchat/network/message/ping_reply.rb +12 -0
- data/lib/meshchat/network/message/whisper.rb +28 -0
- data/lib/meshchat/network/remote.rb +13 -0
- data/lib/meshchat/network/remote/connection.rb +28 -0
- data/lib/meshchat/network/remote/relay.rb +109 -0
- data/lib/meshchat/network/remote/relay_pool.rb +52 -0
- data/lib/meshchat/ui.rb +13 -0
- data/lib/meshchat/ui/cli.rb +48 -0
- data/lib/meshchat/ui/cli/base.rb +39 -0
- data/lib/meshchat/ui/cli/input_factory.rb +50 -0
- data/lib/meshchat/ui/cli/keyboard_line_input.rb +14 -0
- data/lib/meshchat/ui/command.rb +51 -0
- data/lib/meshchat/ui/command/base.rb +77 -0
- data/lib/meshchat/ui/command/bind.rb +47 -0
- data/lib/meshchat/ui/command/chat.rb +31 -0
- data/lib/meshchat/ui/command/config.rb +37 -0
- data/lib/meshchat/ui/command/emote.rb +23 -0
- data/lib/meshchat/ui/command/exit.rb +16 -0
- data/lib/meshchat/ui/command/help.rb +20 -0
- data/lib/meshchat/ui/command/identity.rb +16 -0
- data/lib/meshchat/ui/command/import.rb +42 -0
- data/lib/meshchat/ui/command/irb.rb +22 -0
- data/lib/meshchat/ui/command/offline.rb +23 -0
- data/lib/meshchat/ui/command/online.rb +18 -0
- data/lib/meshchat/ui/command/ping.rb +65 -0
- data/lib/meshchat/ui/command/ping_all.rb +19 -0
- data/lib/meshchat/ui/command/send_disconnect.rb +20 -0
- data/lib/meshchat/ui/command/server.rb +22 -0
- data/lib/meshchat/ui/command/share.rb +16 -0
- data/lib/meshchat/ui/command/whisper.rb +40 -0
- data/lib/meshchat/ui/display.rb +78 -0
- data/lib/meshchat/ui/display/base.rb +58 -0
- data/lib/meshchat/ui/display/manager.rb +59 -0
- data/lib/meshchat/ui/notifier.rb +9 -0
- data/lib/meshchat/ui/notifier/base.rb +33 -0
- data/lib/meshchat/version.rb +3 -2
- metadata +150 -80
- data/lib/meshchat/cli.rb +0 -188
- data/lib/meshchat/cli/base.rb +0 -13
- data/lib/meshchat/cli/input.rb +0 -37
- data/lib/meshchat/command/base.rb +0 -80
- data/lib/meshchat/command/bind.rb +0 -44
- data/lib/meshchat/command/chat.rb +0 -30
- data/lib/meshchat/command/config.rb +0 -34
- data/lib/meshchat/command/emote.rb +0 -20
- data/lib/meshchat/command/exit.rb +0 -13
- data/lib/meshchat/command/help.rb +0 -17
- data/lib/meshchat/command/identity.rb +0 -13
- data/lib/meshchat/command/import.rb +0 -41
- data/lib/meshchat/command/init.rb +0 -34
- data/lib/meshchat/command/irb.rb +0 -23
- data/lib/meshchat/command/listen.rb +0 -13
- data/lib/meshchat/command/offline.rb +0 -20
- data/lib/meshchat/command/online.rb +0 -15
- data/lib/meshchat/command/ping.rb +0 -65
- data/lib/meshchat/command/ping_all.rb +0 -15
- data/lib/meshchat/command/send_disconnect.rb +0 -15
- data/lib/meshchat/command/server.rb +0 -20
- data/lib/meshchat/command/share.rb +0 -13
- data/lib/meshchat/command/stop_listening.rb +0 -13
- data/lib/meshchat/command/whisper.rb +0 -38
- data/lib/meshchat/database.rb +0 -30
- data/lib/meshchat/display.rb +0 -33
- data/lib/meshchat/display/base.rb +0 -60
- data/lib/meshchat/display/manager.rb +0 -55
- data/lib/meshchat/instance.rb +0 -40
- data/lib/meshchat/message.rb +0 -41
- data/lib/meshchat/message/base.rb +0 -97
- data/lib/meshchat/message/chat.rb +0 -19
- data/lib/meshchat/message/disconnect.rb +0 -13
- data/lib/meshchat/message/emote.rb +0 -9
- data/lib/meshchat/message/node_list.rb +0 -63
- data/lib/meshchat/message/node_list_diff.rb +0 -15
- data/lib/meshchat/message/node_list_hash.rb +0 -33
- data/lib/meshchat/message/ping.rb +0 -32
- data/lib/meshchat/message/ping_reply.rb +0 -9
- data/lib/meshchat/message/relay.rb +0 -43
- data/lib/meshchat/message/whisper.rb +0 -36
- data/lib/meshchat/models/entry.rb +0 -104
- data/lib/meshchat/net/client.rb +0 -83
- data/lib/meshchat/net/listener/errors.rb +0 -11
- data/lib/meshchat/net/listener/request.rb +0 -48
- data/lib/meshchat/net/listener/request_processor.rb +0 -50
- data/lib/meshchat/net/listener/server.rb +0 -114
- data/lib/meshchat/net/request.rb +0 -29
- data/lib/meshchat/notifier/base.rb +0 -31
@@ -1,60 +0,0 @@
|
|
1
|
-
module MeshChat
|
2
|
-
module Display
|
3
|
-
class Base
|
4
|
-
|
5
|
-
# instantiate the UI, startup the CLI
|
6
|
-
def start
|
7
|
-
fail 'overload this method'
|
8
|
-
end
|
9
|
-
|
10
|
-
# output a generic line of text
|
11
|
-
def add_line(_line)
|
12
|
-
fail 'overload this method'
|
13
|
-
end
|
14
|
-
|
15
|
-
# formatter for a whisper message
|
16
|
-
def whisper(_line)
|
17
|
-
fail 'overload this method'
|
18
|
-
end
|
19
|
-
|
20
|
-
|
21
|
-
# an emote
|
22
|
-
def emote(_line)
|
23
|
-
fail 'overload this method'
|
24
|
-
end
|
25
|
-
|
26
|
-
|
27
|
-
# server info or other ignorable information
|
28
|
-
def info(_line)
|
29
|
-
fail 'overload this method'
|
30
|
-
end
|
31
|
-
|
32
|
-
# warning message that the user may or may not care about
|
33
|
-
def warning(_line)
|
34
|
-
fail 'overload this method'
|
35
|
-
end
|
36
|
-
|
37
|
-
# really try to get the user's attention
|
38
|
-
def alert(_line)
|
39
|
-
fail 'overload this method'
|
40
|
-
end
|
41
|
-
|
42
|
-
# a happy message to affirm the user something succeded
|
43
|
-
def success(_line)
|
44
|
-
fail 'overload this method'
|
45
|
-
end
|
46
|
-
|
47
|
-
# general chat message
|
48
|
-
def chat(_line)
|
49
|
-
fail 'overload this method'
|
50
|
-
end
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# log a message
|
55
|
-
def log(_msg)
|
56
|
-
fail 'overload this method'
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
require 'meshchat/display/base'
|
2
|
-
|
3
|
-
module MeshChat
|
4
|
-
module Display
|
5
|
-
class Manager
|
6
|
-
|
7
|
-
attr_accessor :_ui
|
8
|
-
|
9
|
-
delegate :start, to: :_ui
|
10
|
-
delegate :add_line, to: :_ui
|
11
|
-
delegate :info, to: :_ui
|
12
|
-
delegate :emote, to: :_ui
|
13
|
-
delegate :warning, to: :_ui
|
14
|
-
delegate :alert, to: :_ui
|
15
|
-
delegate :success, to: :_ui
|
16
|
-
delegate :chat, to: :_ui
|
17
|
-
delegate :whisper, to: :_ui
|
18
|
-
|
19
|
-
attr_accessor :_logger
|
20
|
-
delegate :fatal, to: :_logger
|
21
|
-
delegate :debug, to: :_logger
|
22
|
-
delegate :error, to: :_logger
|
23
|
-
|
24
|
-
def initialize(ui_klass)
|
25
|
-
self._logger = Logger.new('debug.log')
|
26
|
-
self._ui = ui_klass.new
|
27
|
-
end
|
28
|
-
|
29
|
-
def present_message(message)
|
30
|
-
result = message.handle
|
31
|
-
return unless result
|
32
|
-
|
33
|
-
case message.class.name
|
34
|
-
when Message::Chat.name
|
35
|
-
chat result
|
36
|
-
Notify.show(summary: message.sender_name, body: message.message)
|
37
|
-
when Message::Whisper.name
|
38
|
-
whisper result
|
39
|
-
Notify.show(summary: message.sender_name, body: message.message)
|
40
|
-
when Message::Emote.name
|
41
|
-
emote result
|
42
|
-
Notify.show(summary: message.sender_name, body: message.message)
|
43
|
-
when Message::PingReply.name, Message::Ping.name
|
44
|
-
info result
|
45
|
-
when Message::NodeList.name,
|
46
|
-
Message::NodeListDiff.name,
|
47
|
-
Message::NodeListHash.name
|
48
|
-
# display nothing
|
49
|
-
else
|
50
|
-
add_line result
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
data/lib/meshchat/instance.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
module MeshChat
|
2
|
-
class Instance
|
3
|
-
attr_accessor :client_name, :client_version, :display
|
4
|
-
|
5
|
-
class << self
|
6
|
-
delegate :client_name, :client_version, :display,
|
7
|
-
to: :instance
|
8
|
-
|
9
|
-
def start(options)
|
10
|
-
# calling instance to get things going
|
11
|
-
@instance = new(options)
|
12
|
-
@instance.start_input_device(options[:input])
|
13
|
-
@instance.start_ui(options[:display], options[:on_display_start])
|
14
|
-
end
|
15
|
-
|
16
|
-
def instance
|
17
|
-
@instance
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def initialize(options = {})
|
22
|
-
self.client_name = options[:client_name] || MeshChat::NAME
|
23
|
-
self.client_version = options[:client_version] || MeshChat::VERSION
|
24
|
-
end
|
25
|
-
|
26
|
-
# @param [Class] klass should be something that implements Display::Base
|
27
|
-
# @param [Proc] proc what to do when starting the UI
|
28
|
-
def start_ui(klass, on_display_start)
|
29
|
-
self.display = Display::Manager.new(klass)
|
30
|
-
display.start do
|
31
|
-
on_display_start.call if on_display_start
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def start_input_device(klass)
|
36
|
-
# The CLI is a singleton that takes a method of input
|
37
|
-
MeshChat::CLI.create(klass)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
data/lib/meshchat/message.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
require 'meshchat/message/base'
|
2
|
-
require 'meshchat/message/chat'
|
3
|
-
require 'meshchat/message/emote'
|
4
|
-
require 'meshchat/message/ping'
|
5
|
-
require 'meshchat/message/ping_reply'
|
6
|
-
require 'meshchat/message/disconnect'
|
7
|
-
require 'meshchat/message/whisper'
|
8
|
-
require 'meshchat/message/relay'
|
9
|
-
require 'meshchat/message/node_list'
|
10
|
-
require 'meshchat/message/node_list_diff'
|
11
|
-
require 'meshchat/message/node_list_hash'
|
12
|
-
|
13
|
-
module MeshChat
|
14
|
-
module Message
|
15
|
-
CHAT = 'chat'
|
16
|
-
EMOTE = 'emote'
|
17
|
-
PING = 'ping'
|
18
|
-
PING_REPLY = 'pingreply'
|
19
|
-
WHISPER = 'whisper'
|
20
|
-
RELAY = 'relay'
|
21
|
-
DISCONNECT = 'disconnect'
|
22
|
-
|
23
|
-
NODE_LIST = 'nodelist'
|
24
|
-
NODE_LIST_HASH = 'nodelisthash'
|
25
|
-
NODE_LIST_DIFF = 'nodelistdiff'
|
26
|
-
|
27
|
-
TYPES = {
|
28
|
-
CHAT => Chat,
|
29
|
-
EMOTE => Emote,
|
30
|
-
WHISPER => Whisper,
|
31
|
-
DISCONNECT => Disconnect,
|
32
|
-
PING => Ping,
|
33
|
-
PING_REPLY => PingReply,
|
34
|
-
NODE_LIST => NodeList,
|
35
|
-
NODE_LIST_DIFF => NodeListDiff,
|
36
|
-
NODE_LIST_HASH => NodeListHash
|
37
|
-
}
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
end
|
@@ -1,97 +0,0 @@
|
|
1
|
-
module MeshChat
|
2
|
-
module Message
|
3
|
-
#
|
4
|
-
# NOTE:
|
5
|
-
# #display: shows the message
|
6
|
-
# should be used locally, before *sending* a message
|
7
|
-
# #handle: processing logic for the message
|
8
|
-
# should be used when receiving a message, and there
|
9
|
-
# needs to be a response right away
|
10
|
-
# #respond: where the actual logic for the response goes
|
11
|
-
class Base
|
12
|
-
attr_accessor :payload, :time_recieved,
|
13
|
-
:message, :sender_name, :sender_location, :sender_uid
|
14
|
-
|
15
|
-
# @param [String] message
|
16
|
-
# @param [String] name_of_sender
|
17
|
-
# @param [String] location the location of the sender
|
18
|
-
# @param [Hash] payload optionally overrides the default payload
|
19
|
-
def initialize(
|
20
|
-
message: '',
|
21
|
-
sender_name: '',
|
22
|
-
sender_location: '',
|
23
|
-
sender_uid: '',
|
24
|
-
time_recieved: nil,
|
25
|
-
payload: nil)
|
26
|
-
|
27
|
-
@payload = payload.presence
|
28
|
-
|
29
|
-
# TODO: find a more elegant way to represent this
|
30
|
-
self.message = message.presence || @payload.try(:[], 'message')
|
31
|
-
self.sender_name = sender_name.presence || @payload.try(:[], 'sender').try(:[], 'alias') || Settings['alias']
|
32
|
-
self.sender_location = sender_location.presence || @payload.try(:[], 'sender').try(:[], 'location') || Settings.location
|
33
|
-
self.sender_uid = sender_uid.presence || @payload.try(:[], 'sender').try(:[], 'uid') || Settings['uid']
|
34
|
-
self.time_recieved = time_recieved.presence || Time.now
|
35
|
-
|
36
|
-
end
|
37
|
-
|
38
|
-
def payload
|
39
|
-
@payload ||= {
|
40
|
-
'type' => type,
|
41
|
-
'message' => message,
|
42
|
-
'client' => client,
|
43
|
-
'client_version' => client_version,
|
44
|
-
'time_sent' => time_recieved || Time.now.to_s,
|
45
|
-
'sender' => {
|
46
|
-
'alias' => sender_name,
|
47
|
-
'location' => sender_location,
|
48
|
-
'uid' => sender_uid
|
49
|
-
}
|
50
|
-
}
|
51
|
-
end
|
52
|
-
|
53
|
-
def type
|
54
|
-
@type ||= TYPES.invert[self.class]
|
55
|
-
end
|
56
|
-
|
57
|
-
def client
|
58
|
-
MeshChat.name
|
59
|
-
end
|
60
|
-
|
61
|
-
def client_version
|
62
|
-
MeshChat.version
|
63
|
-
end
|
64
|
-
|
65
|
-
def sender
|
66
|
-
payload['sender']
|
67
|
-
end
|
68
|
-
|
69
|
-
# shows the message
|
70
|
-
# should be used locally, before *sending* a message
|
71
|
-
def display
|
72
|
-
message
|
73
|
-
end
|
74
|
-
|
75
|
-
# processing logic for the message
|
76
|
-
# should be used when receiving a message, and there
|
77
|
-
# needs to be a response right away.
|
78
|
-
# this may call display, if the response is always to be displayed
|
79
|
-
def handle
|
80
|
-
display
|
81
|
-
end
|
82
|
-
|
83
|
-
# Most message types aren't going to need to have an
|
84
|
-
# immediate response.
|
85
|
-
#
|
86
|
-
def respond
|
87
|
-
return
|
88
|
-
end
|
89
|
-
|
90
|
-
# this message should be called immediately
|
91
|
-
# before sending to the whomever
|
92
|
-
def render
|
93
|
-
payload.to_json
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module MeshChat
|
2
|
-
module Message
|
3
|
-
class Chat < Base
|
4
|
-
|
5
|
-
def display
|
6
|
-
time_recieved = self.time_recieved.strftime('%e/%m/%y %H:%I:%M')
|
7
|
-
name = payload['sender']['alias']
|
8
|
-
message = payload['message']
|
9
|
-
|
10
|
-
format_display(time_recieved, name, message)
|
11
|
-
end
|
12
|
-
|
13
|
-
|
14
|
-
def format_display(time, name, message)
|
15
|
-
"#{time} #{name} > #{message}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
module MeshChat
|
2
|
-
module Message
|
3
|
-
class Disconnect < Base
|
4
|
-
def display
|
5
|
-
location = payload['sender']['location']
|
6
|
-
name = payload['sender']['alias']
|
7
|
-
node = Node.find_by_location(location)
|
8
|
-
node.update(online: false) if node
|
9
|
-
"#{name}@#{location} has disconnected"
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
@@ -1,63 +0,0 @@
|
|
1
|
-
module MeshChat
|
2
|
-
module Message
|
3
|
-
class NodeList < Base
|
4
|
-
def message
|
5
|
-
@message ||= Node.as_json
|
6
|
-
end
|
7
|
-
|
8
|
-
def handle
|
9
|
-
respond
|
10
|
-
return
|
11
|
-
end
|
12
|
-
|
13
|
-
# only need to respond if this server has node entries that the
|
14
|
-
# sender of this message doesn't have
|
15
|
-
def respond
|
16
|
-
received_list = message
|
17
|
-
we_only_have, they_only_have = Node.diff(received_list)
|
18
|
-
|
19
|
-
Display.debug('node_list#respond: me: ' + we_only_have.to_s)
|
20
|
-
Display.debug('node_list#respond: they: ' + they_only_have.to_s)
|
21
|
-
|
22
|
-
if they_only_have.present?
|
23
|
-
they_only_have.each do |n|
|
24
|
-
Node.from_json(n).save!
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
|
29
|
-
location = payload['sender']['location']
|
30
|
-
|
31
|
-
node = Node.find_by_location(location)
|
32
|
-
if we_only_have.present?
|
33
|
-
Display.debug 'we have nodes that they do not'
|
34
|
-
|
35
|
-
# give the sender our list
|
36
|
-
MeshChat::Net::Client.send(
|
37
|
-
node: node,
|
38
|
-
message: NodeListDiff.new(message: we_only_have)
|
39
|
-
)
|
40
|
-
|
41
|
-
# give people we know about
|
42
|
-
# (but the sender of the Node List may not know about)
|
43
|
-
# our node list diff
|
44
|
-
Node.online.each do |entry|
|
45
|
-
MeshChat::Net::Client.send(
|
46
|
-
node: entry,
|
47
|
-
message: NodeListDiff.new(message: they_only_have)
|
48
|
-
)
|
49
|
-
end
|
50
|
-
else
|
51
|
-
Display.debug 'node lists are in sync'
|
52
|
-
|
53
|
-
# lists are in sync, confirm with hash
|
54
|
-
MeshChat::Net::Client.send(
|
55
|
-
node: node,
|
56
|
-
message: NodeListHash.new
|
57
|
-
)
|
58
|
-
end
|
59
|
-
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
module MeshChat
|
2
|
-
module Message
|
3
|
-
class NodeListDiff < Base
|
4
|
-
def handle
|
5
|
-
entries_we_do_not_have = message
|
6
|
-
|
7
|
-
entries_we_do_not_have.each do |entry_as_json|
|
8
|
-
# this will silently fail if there is a duplicate
|
9
|
-
# or if this is an invalid entry
|
10
|
-
Node.from_json(entry_as_json).save
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
module MeshChat
|
2
|
-
module Message
|
3
|
-
class NodeListHash < Base
|
4
|
-
def message
|
5
|
-
@message ||= Node.as_sha512
|
6
|
-
end
|
7
|
-
|
8
|
-
# node list hash is received
|
9
|
-
# @return [NilClass] no output for this message type
|
10
|
-
def handle
|
11
|
-
respond
|
12
|
-
return
|
13
|
-
end
|
14
|
-
|
15
|
-
def respond
|
16
|
-
if message != Node.as_sha512
|
17
|
-
Display.debug 'node list hashes do not match'
|
18
|
-
location = payload['sender']['location']
|
19
|
-
|
20
|
-
Display.debug 'sending a node list to ' + location
|
21
|
-
node = Node.find_by_location(location)
|
22
|
-
|
23
|
-
MeshChat::Net::Client.send(
|
24
|
-
node: node,
|
25
|
-
message: NodeList.new(message: Node.as_json)
|
26
|
-
)
|
27
|
-
else
|
28
|
-
Display.debug 'node list hash matches'
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|