frypan 0.0.1 → 1.0.0
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/.gitignore +1 -1
- data/README.md +33 -0
- data/Rakefile +2 -0
- data/frypan.gemspec +3 -4
- data/lib/frypan.rb +191 -52
- data/lib/frypan/version.rb +1 -1
- data/tutorial/my_at.rb +17 -0
- data/tutorial/my_atd.rb +85 -0
- data/tutorial/sample_tcp_client.rb +8 -0
- data/tutorial/sample_tcp_server.rb +13 -0
- data/tutorial/tutorial.md +359 -0
- data/unit_test/test_frypan.rb +66 -0
- metadata +14 -14
- data/_README.md +0 -31
- data/bin/frypan +0 -55
- data/lib/frypan/database_sync_list.rb +0 -30
- data/lib/frypan/epg_parser.rb +0 -23
- data/lib/frypan/node.rb +0 -150
- data/lib/frypan/tuner.rb +0 -47
- data/unit_test/test_node.rb +0 -171
data/_README.md
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
# Frypan
|
2
|
-
|
3
|
-
TODO: Write a gem description
|
4
|
-
|
5
|
-
## Installation
|
6
|
-
|
7
|
-
Add this line to your application's Gemfile:
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
gem 'frypan'
|
11
|
-
```
|
12
|
-
|
13
|
-
And then execute:
|
14
|
-
|
15
|
-
$ bundle
|
16
|
-
|
17
|
-
Or install it yourself as:
|
18
|
-
|
19
|
-
$ gem install frypan
|
20
|
-
|
21
|
-
## Usage
|
22
|
-
|
23
|
-
TODO: Write usage instructions here
|
24
|
-
|
25
|
-
## Contributing
|
26
|
-
|
27
|
-
1. Fork it ( https://github.com/[my-github-username]/frypan/fork )
|
28
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
30
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
-
5. Create a new Pull Request
|
data/bin/frypan
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# -*- coding: utf-8 -*-
|
3
|
-
# -*- mode: ruby -*-
|
4
|
-
|
5
|
-
require 'mongo'
|
6
|
-
|
7
|
-
def install(home_path)
|
8
|
-
# 1. mongodbのバイナリを落として [homepath]/mongodb.tarに保存
|
9
|
-
# 2. 解凍する。[homepath]/mongodb/bin/mongodってな具合になる
|
10
|
-
# 3. [homepath]/db/作る
|
11
|
-
end
|
12
|
-
|
13
|
-
def fork_db(home_path, port=27017)
|
14
|
-
mongod = "#{home_path}/mongodb/bin/mongod"
|
15
|
-
db = "#{home_path}/db/"
|
16
|
-
log = "#{home_path}/db/server.log"
|
17
|
-
status = `#{mongod} --dbpath #{db} --logpath #{log} --port #{port} --fork`
|
18
|
-
pid = status.lines.map{|ln| ln.match(/forked process: (\d+)/)}.select{|a| a}.first[1].to_i
|
19
|
-
success = status.lines.any?{|ln| ln.match(/successfully/)}
|
20
|
-
if success then pid else nil end
|
21
|
-
end
|
22
|
-
|
23
|
-
def connect_db(port=27017, host="localhost")
|
24
|
-
Mongo::Connection.new(host, port).db("frypan_core")
|
25
|
-
end
|
26
|
-
|
27
|
-
begin
|
28
|
-
pid = fork_db("~/Workspace/")
|
29
|
-
TinyFRP::Runtime.new(main_signal(connect_db)).run(:loop)
|
30
|
-
ensure
|
31
|
-
Process.kill("TERM", pid) if pid
|
32
|
-
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
db = hoge
|
37
|
-
api_server = APIServer.new(db) # fork webrick. only op-command throw.
|
38
|
-
epg = hoge
|
39
|
-
tuner = [fuga]
|
40
|
-
|
41
|
-
command = TinyFRP.lift{ api_server.get_next_command } # one-unit-command OR nil
|
42
|
-
current_time = TinyFRP.lift{ Time.now.strftime("%Y%m%d%H%M%S") }
|
43
|
-
epg_flag = hoge
|
44
|
-
tuners_flag = [fuga]
|
45
|
-
|
46
|
-
main = TinyFRP::Bundle.new(command, current_time, epg_flag, tuners_flag) >> Node.top(setting)
|
47
|
-
|
48
|
-
TinyFRP.loop(main) do |out|
|
49
|
-
out[:reserve1].action(db)
|
50
|
-
out[:reserve2].action(db)
|
51
|
-
out[:reserve3].action(db)
|
52
|
-
out[:reserve4].action(db)
|
53
|
-
out[:epg].action(epg)
|
54
|
-
out[:tuner1].action(tuner1)
|
55
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
module Frypan
|
2
|
-
class DBSyncList
|
3
|
-
def initialize(name, db)
|
4
|
-
@coll = db.collection(name)
|
5
|
-
end
|
6
|
-
|
7
|
-
def +(list)
|
8
|
-
list.each{|a| @coll.insert(a)}
|
9
|
-
self
|
10
|
-
end
|
11
|
-
|
12
|
-
def update(alt_list)
|
13
|
-
@coll.remove
|
14
|
-
alt_list.each{|a| @coll.insert(a)}
|
15
|
-
self
|
16
|
-
end
|
17
|
-
|
18
|
-
def update_elements(elements, ident_key)
|
19
|
-
elements.each do |a|
|
20
|
-
@coll.update({ident_key => a[ident_key]}, {"$set" => a}, true)
|
21
|
-
end
|
22
|
-
self
|
23
|
-
end
|
24
|
-
|
25
|
-
def to_a
|
26
|
-
@coll.find.to_a
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
data/lib/frypan/epg_parser.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
module Frypan
|
2
|
-
class EpgParser
|
3
|
-
require 'm2ts_parser'
|
4
|
-
class ParserProcess
|
5
|
-
def initialize(file_path)
|
6
|
-
|
7
|
-
end
|
8
|
-
|
9
|
-
def finished?
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
def get_parsed
|
14
|
-
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def parse(file_path)
|
19
|
-
ParserProcess.new(file_path)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
data/lib/frypan/node.rb
DELETED
@@ -1,150 +0,0 @@
|
|
1
|
-
module Frypan
|
2
|
-
require 'tiny_frp'
|
3
|
-
module Node
|
4
|
-
def self.overlaped?(list_of_range)
|
5
|
-
!list_of_range.sort{|a, b| a.begin <=> b.begin}.each_cons(2).all?{|a, b| a.end <= b.begin}
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.apply_operations(list, ops)
|
9
|
-
case
|
10
|
-
when ops == []
|
11
|
-
[list, ""]
|
12
|
-
when ops.first[:kind] == :add
|
13
|
-
added_list = list + [ops.first[:target]]
|
14
|
-
if overlaped?(added_list.map{|a| a[:start_time]..a[:end_time]})
|
15
|
-
[nil, "cannot add reservation: #{ops.first[:target]}"]
|
16
|
-
else
|
17
|
-
apply_operations(added_list, ops.drop(1))
|
18
|
-
end
|
19
|
-
when ops.first[:kind] == :remove
|
20
|
-
if list.find{|a| a[:start_time] == ops.first[:target][:start_time]}
|
21
|
-
apply_operations(list.reject{|a| a[:start_time] == ops.first[:target][:start_time]}, ops.drop(1))
|
22
|
-
else
|
23
|
-
[nil, "unexist remove reservation id: #{ops.first[:target][:id]}"]
|
24
|
-
end
|
25
|
-
else
|
26
|
-
[nil, "unknown operation '#{ops.first[:kind]}'"]
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.ReserveListFoldp(tuner_id, initial_list=[])
|
31
|
-
TinyFRP.foldp(launch: nil, list: initial_list, ack: nil) do |acc, command, current_time|
|
32
|
-
case
|
33
|
-
when command && command[:target_tuner] == tuner_id
|
34
|
-
new_list, status = *apply_operations(acc[:list], command[:operations])
|
35
|
-
ack = command.merge({
|
36
|
-
timestamp: current_time,
|
37
|
-
succeeded: !!new_list,
|
38
|
-
status: status
|
39
|
-
})
|
40
|
-
if new_list
|
41
|
-
{launch: nil, list: new_list.sort{|a, b| a[:start_time] <=> b[:start_time]}, ack: ack}
|
42
|
-
else
|
43
|
-
{launch: nil, ack: ack}
|
44
|
-
end
|
45
|
-
when acc[:list] != [] && acc[:list].first[:start_time] <= current_time
|
46
|
-
{launch: acc[:list].first, list: acc[:list].drop(1), ack: nil}
|
47
|
-
else
|
48
|
-
{launch: nil, ack: nil}
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.PersistantReserveListFoldp(tuner_id, dbsync_reserve)
|
54
|
-
TinyFRP.foldp(last_list: nil, list: dbsync_reserve) do |acc, rlist|
|
55
|
-
if !acc[:last_list] || acc[:last_list] != rlist[:list]
|
56
|
-
{last_list: rlist[:list], list: acc[:list].update(rlist[:list])}
|
57
|
-
else
|
58
|
-
{}
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.RecordingAsyncFoldp(tuner)
|
64
|
-
TinyFRP.foldp(busy: false, rec_process: nil, waiting: [], log: []) do |acc, rlist|
|
65
|
-
case
|
66
|
-
when rlist[:launch]
|
67
|
-
{waiting: acc[:waiting] + [rlist[:launch]], log: []}
|
68
|
-
when acc[:busy]
|
69
|
-
{busy: !acc[:rec_process].finished?, log: acc[:rec_process].get_log}
|
70
|
-
when acc[:waiting] != []
|
71
|
-
{
|
72
|
-
busy: true,
|
73
|
-
rec_process: tuner.rec(acc[:waiting].first),
|
74
|
-
waiting: acc[:waiting].drop(1),
|
75
|
-
log: []
|
76
|
-
}
|
77
|
-
else
|
78
|
-
{log: []}
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def self.RecordingLogFoldp(initial_log=[])
|
84
|
-
TinyFRP.foldp(log: initial_log, epg_order: [], recorded: []) do |acc, *log_inputs|
|
85
|
-
if log_inputs.any?{|a| a != []}
|
86
|
-
new_logs = log_inputs.select{|a| a != []}.flatten
|
87
|
-
{
|
88
|
-
log: acc[:log] + new_logs,
|
89
|
-
epg_order: Fnc.find_epg_order(new_logs),
|
90
|
-
recorded: Fnc.find_recorded(new_logs)
|
91
|
-
}
|
92
|
-
else
|
93
|
-
{epg_order: [], recorded: []}
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def self.EpgExtractAsyncFoldp(epg_parser)
|
99
|
-
TinyFRP.foldp(busy: false, parser_process: nil, waiting: [], extracted: []) do |acc, log|
|
100
|
-
case
|
101
|
-
when log[:epg_order]
|
102
|
-
{waiting: acc[:waiting] + [log[:epg_order]], extracted: []}
|
103
|
-
when acc[:busy]
|
104
|
-
{busy: !acc[:parser_process].finished?, extracted: acc[:parser_process].get_parsed}
|
105
|
-
when acc[:waiting] != []
|
106
|
-
{
|
107
|
-
busy: true,
|
108
|
-
parser_process: epg_parser.parse(acc[:waiting].first[:file_path]),
|
109
|
-
waiting: acc[:waiting].drop(1),
|
110
|
-
extracted: []
|
111
|
-
}
|
112
|
-
else
|
113
|
-
{extracted: []}
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def self.ProgramListFoldp(initial_list=[])
|
119
|
-
TinyFRP.foldp(list: initial_list) do |acc, epg|
|
120
|
-
if epg[:extracted] != []
|
121
|
-
# epg[:extracted] := list of {program_id: [service_id, event_id], ...}
|
122
|
-
{list: acc[:list].update_elements(epg[:extracted], :program_id)}
|
123
|
-
else
|
124
|
-
{}
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
def self.RecordedListFoldp(initial_list=[])
|
130
|
-
TinyFRP.foldp(list: initial_list) do |acc, log|
|
131
|
-
if log[:recorded] != []
|
132
|
-
{list: acc[:list] + log[:recorded]}
|
133
|
-
else
|
134
|
-
{}
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def self.UIresponseFoldp(initial_list=[])
|
140
|
-
TinyFRP.foldp(list: initial_list) do |acc, current_time, *acks|
|
141
|
-
if acks.any?{|a| a}
|
142
|
-
{list: acc[:list] + acks.select{|a| a}}
|
143
|
-
else
|
144
|
-
{}
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
data/lib/frypan/tuner.rb
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
module Frypan
|
2
|
-
class Tuner
|
3
|
-
class RecordingProcess
|
4
|
-
def initialize(command)
|
5
|
-
@reader, writer = IO.pipe
|
6
|
-
@pid = Process.spawn(command, :err => writer, :out => writer)
|
7
|
-
@mutex = Mutex.new
|
8
|
-
@logs = []
|
9
|
-
Thread.new{
|
10
|
-
while IO::select([@reader])
|
11
|
-
@mutex.synchronize{
|
12
|
-
@logs << @reader.gets.chomp
|
13
|
-
}
|
14
|
-
end
|
15
|
-
}
|
16
|
-
end
|
17
|
-
|
18
|
-
def finished?
|
19
|
-
@finished ||= Process.waitpid2(@pid, Process::WNOHANG)
|
20
|
-
end
|
21
|
-
|
22
|
-
def get_log
|
23
|
-
res = []
|
24
|
-
@mutex.synchronize{
|
25
|
-
res = @logs.dup
|
26
|
-
@logs = []
|
27
|
-
}
|
28
|
-
res
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def initialize(format_str)
|
33
|
-
@format_str = format_str
|
34
|
-
end
|
35
|
-
|
36
|
-
def rec(setting)
|
37
|
-
RecordingProcess.new(Tuner.make_command(@format_str, setting))
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.make_command(format_str, setting)
|
41
|
-
setting.keys.inject(format_str) do |str, key|
|
42
|
-
str.gsub("<#{key}>", setting[key].to_s)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
data/unit_test/test_node.rb
DELETED
@@ -1,171 +0,0 @@
|
|
1
|
-
$LIBRARY_ROOT_PATH = File.dirname(File.expand_path(File.dirname(__FILE__)))
|
2
|
-
|
3
|
-
module Frypan
|
4
|
-
module UnitTest
|
5
|
-
require 'test/unit'
|
6
|
-
require $LIBRARY_ROOT_PATH + '/lib/frypan/node.rb'
|
7
|
-
|
8
|
-
class DummyList
|
9
|
-
attr_reader :array
|
10
|
-
def initialize(init=[])
|
11
|
-
@array = []
|
12
|
-
end
|
13
|
-
|
14
|
-
def +(list)
|
15
|
-
DummyList.new(@array + list)
|
16
|
-
end
|
17
|
-
|
18
|
-
def update(alt_list)
|
19
|
-
DummyList.new(alt_list)
|
20
|
-
end
|
21
|
-
|
22
|
-
def update_elements(elements, ident_key)
|
23
|
-
if elements == []
|
24
|
-
self
|
25
|
-
else
|
26
|
-
update_element(elements.first, ident_key).update_elements(elements.drop(1), ident_key)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def update_element(element, ident_key)
|
31
|
-
if @array.find{|a| a[ident_key] == element[ident_key]}
|
32
|
-
DummyList.new(@array.map{|a| a[ident_key] == element[ident_key] ? element : a})
|
33
|
-
else
|
34
|
-
DummyList.new(@array + [element])
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
class DummyTuner
|
40
|
-
class DummyRecordingProcess
|
41
|
-
def initialize(logs)
|
42
|
-
@logs = logs.dup
|
43
|
-
end
|
44
|
-
|
45
|
-
def finished?
|
46
|
-
@logs.empty?
|
47
|
-
end
|
48
|
-
|
49
|
-
def get_log
|
50
|
-
@logs.shift || []
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def initialize(logs)
|
55
|
-
@logs = logs
|
56
|
-
end
|
57
|
-
|
58
|
-
def rec(setting)
|
59
|
-
DummyRecordingProcess.new(@logs)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
class DummyEpgParser
|
64
|
-
class DummyParserProcess
|
65
|
-
def initialize(parseds)
|
66
|
-
@parseds = parseds.dup
|
67
|
-
end
|
68
|
-
|
69
|
-
def finished?
|
70
|
-
@parseds.empty?
|
71
|
-
end
|
72
|
-
|
73
|
-
def get_parsed
|
74
|
-
@parseds.shift || []
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def initialize(parseds)
|
79
|
-
@parseds = parseds
|
80
|
-
end
|
81
|
-
|
82
|
-
def parse(file_path)
|
83
|
-
DummyParserProcess.new(@parseds)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
module Util
|
88
|
-
def mkcom(command_id, target_tuner, *ops)
|
89
|
-
{command_id: command_id, target_tuner: target_tuner, operations: ops}
|
90
|
-
end
|
91
|
-
|
92
|
-
def mkop(kind, target)
|
93
|
-
{kind: kind, target: target}
|
94
|
-
end
|
95
|
-
|
96
|
-
def mkres(id, start_time, end_time)
|
97
|
-
{id: id, start_time: start_time, end_time: end_time}
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
class NodeTest < Test::Unit::TestCase
|
102
|
-
include Util
|
103
|
-
|
104
|
-
def test_ReserveListFoldp
|
105
|
-
n = Node.ReserveListFoldp(:test_tuner, [])
|
106
|
-
vals = [
|
107
|
-
nil,
|
108
|
-
com1 = mkcom(0, :test_tuner, mkop(:add, res1 = mkres(0, 2, 5))),
|
109
|
-
com2 = mkcom(1, :test_tuner, mkop(:add, res2 = mkres(1, 5, 7))),
|
110
|
-
nil,
|
111
|
-
com3 = mkcom(2, :test_tuner, mkop(:add, res3 = mkres(2, 6, 10)))
|
112
|
-
].each_with_index.map{|a, i| n.call(a, i)}
|
113
|
-
|
114
|
-
assert_equal({launch: nil, list: [], ack: nil}, vals[0])
|
115
|
-
|
116
|
-
expect_ack1 = com1.merge(timestamp: 1, succeeded: true, status: "")
|
117
|
-
assert_equal({launch: nil, list: [res1], ack: expect_ack1}, vals[1])
|
118
|
-
|
119
|
-
expect_ack2 = com2.merge(timestamp: 2, succeeded: true, status: "")
|
120
|
-
assert_equal({launch: nil, list: [res1, res2], ack: expect_ack2}, vals[2])
|
121
|
-
|
122
|
-
assert_equal({launch: res1, list: [res2], ack: nil}, vals[3])
|
123
|
-
|
124
|
-
expect_ack3 = com3.merge(timestamp: 4, succeeded: false, status: vals[4][:ack][:status])
|
125
|
-
assert_equal({launch: nil, list: [res2], ack: expect_ack3}, vals[4])
|
126
|
-
end
|
127
|
-
|
128
|
-
def test_PersistantReserveListFoldp
|
129
|
-
n = Node.PersistantReserveListFoldp(:tuner_tuner, DummyList.new([]))
|
130
|
-
vals = [
|
131
|
-
{list: [:a]},
|
132
|
-
{list: [:a]},
|
133
|
-
{list: [:a, :b]},
|
134
|
-
].map{|a| n.call(a)}
|
135
|
-
|
136
|
-
assert_equal(vals[0], vals[1])
|
137
|
-
assert_not_equal(vals[1], vals[2])
|
138
|
-
end
|
139
|
-
|
140
|
-
def test_RecordingAsyncFoldp
|
141
|
-
n = Node.RecordingAsyncFoldp(DummyTuner.new([[0, 1], [2]]))
|
142
|
-
v = [
|
143
|
-
{launch: nil},
|
144
|
-
{launch: :a},
|
145
|
-
{launch: nil},
|
146
|
-
{launch: :b},
|
147
|
-
{launch: nil},
|
148
|
-
{launch: nil},
|
149
|
-
{launch: nil},
|
150
|
-
{launch: nil},
|
151
|
-
{launch: nil},
|
152
|
-
{launch: nil},
|
153
|
-
{launch: nil},
|
154
|
-
].map{|a| n.call(a)}
|
155
|
-
|
156
|
-
assert_equal({busy: false, rec_process: v[0][:rec_process], waiting: [], log: []}, v[0])
|
157
|
-
assert_equal({busy: false, rec_process: v[1][:rec_process], waiting: [:a], log: []}, v[1])
|
158
|
-
assert_equal({busy: true, rec_process: v[2][:rec_process], waiting: [], log: []}, v[2])
|
159
|
-
assert_equal({busy: true, rec_process: v[3][:rec_process], waiting: [:b], log: []}, v[3])
|
160
|
-
assert_equal({busy: true, rec_process: v[4][:rec_process], waiting: [:b], log: [0, 1]}, v[4])
|
161
|
-
assert_equal({busy: true, rec_process: v[5][:rec_process], waiting: [:b], log: [2]}, v[5])
|
162
|
-
assert_equal({busy: false, rec_process: v[6][:rec_process], waiting: [:b], log: []}, v[6])
|
163
|
-
assert_equal({busy: true, rec_process: v[7][:rec_process], waiting: [], log: []}, v[7])
|
164
|
-
assert_equal({busy: true, rec_process: v[8][:rec_process], waiting: [], log: [0, 1]}, v[8])
|
165
|
-
assert_equal({busy: true, rec_process: v[9][:rec_process], waiting: [], log: [2]}, v[9])
|
166
|
-
assert_equal({busy: false, rec_process: v[10][:rec_process], waiting: [], log: []}, v[10])
|
167
|
-
end
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|