vagrant-node 1.0.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/lib/vagrant-node/actions/boxadd.rb +110 -0
- data/lib/vagrant-node/actions/snapshot.rb +1 -1
- data/lib/vagrant-node/api.rb +303 -270
- data/lib/vagrant-node/apidesc.rb +47 -21
- data/lib/vagrant-node/clientcontroller.rb +658 -415
- data/lib/vagrant-node/configmanager.rb +266 -85
- data/lib/vagrant-node/dbmanager.rb +300 -123
- data/lib/vagrant-node/exceptions.rb +15 -0
- data/lib/vagrant-node/nodeserverpasswd.rb +109 -34
- data/lib/vagrant-node/nodeserverstart.rb +4 -3
- data/lib/vagrant-node/nodeserverstop.rb +2 -1
- data/lib/vagrant-node/obmanager.rb +123 -0
- data/lib/vagrant-node/server.rb +27 -21
- data/lib/vagrant-node/util/downloader.rb +182 -0
- data/lib/vagrant-node/version.rb +1 -1
- data/vagrant-node.gemspec +17 -3
- metadata +65 -22
- data/lib/vagrant-node/actions/.svn/entries +0 -62
- data/lib/vagrant-node/actions/.svn/text-base/snapshot.rb.svn-base +0 -310
@@ -14,6 +14,21 @@ module Vagrant
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
class VMActionException < StandardError
|
18
|
+
def initialize(vmname,provider,msg)
|
19
|
+
super(msg)
|
20
|
+
@vmname = vmname
|
21
|
+
@provider = provider
|
22
|
+
end
|
23
|
+
def vmname
|
24
|
+
@vmname
|
25
|
+
end
|
26
|
+
|
27
|
+
def provider
|
28
|
+
@provider
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
17
32
|
class ExceptionMutator < RestException
|
18
33
|
include Vagrant::Errors
|
19
34
|
def initialize(exception)
|
@@ -6,6 +6,25 @@ require 'vagrant-node/dbmanager'
|
|
6
6
|
module Vagrant
|
7
7
|
module Node
|
8
8
|
class NodeServerPasswd < Vagrant.plugin(2, :command)
|
9
|
+
|
10
|
+
def ask_for_config
|
11
|
+
puts "Configuring Vagrant Node"
|
12
|
+
puts "Insert database user:"
|
13
|
+
user=STDIN.noecho(&:gets).chomp
|
14
|
+
print "\n"
|
15
|
+
print "Insert database password:"
|
16
|
+
password=STDIN.noecho(&:gets).chomp
|
17
|
+
print "\n"
|
18
|
+
print "Insert database name:"
|
19
|
+
database=STDIN.noecho(&:gets).chomp
|
20
|
+
print "\n"
|
21
|
+
|
22
|
+
DB::DBManager.create_config_file(@env.data_dir,'localhost',database,user,password)
|
23
|
+
|
24
|
+
DB::DBManager.check_bbdd_structure
|
25
|
+
|
26
|
+
end
|
27
|
+
|
9
28
|
def execute
|
10
29
|
options = {}
|
11
30
|
|
@@ -20,42 +39,98 @@ module Vagrant
|
|
20
39
|
|
21
40
|
# if (argv.length==0)
|
22
41
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
42
|
+
i=0
|
43
|
+
|
44
|
+
|
45
|
+
begin
|
46
|
+
|
47
|
+
if (!DB::DBManager.check_config_file(@env.data_dir))
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
puts "Configuring Vagrant Node"
|
52
|
+
print "Insert privileged database user: "
|
53
|
+
user=STDIN.cooked(&:gets).chomp
|
54
|
+
|
55
|
+
print "Insert privileged database user password: "
|
56
|
+
password=STDIN.noecho(&:gets).chomp
|
57
|
+
print "\n"
|
58
|
+
print "Insert database name: "
|
59
|
+
database=STDIN.cooked(&:gets).chomp
|
60
|
+
print "\n"
|
61
|
+
|
62
|
+
DB::DBManager.create_config_file(@env.data_dir,'localhost',database,user,password)
|
63
|
+
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
db = DB::DBManager.new(@env.data_dir)
|
69
|
+
|
70
|
+
rescue Exception => e
|
71
|
+
|
72
|
+
if (!DB::DBManager.check_config_file(@env.data_dir))
|
73
|
+
retry
|
74
|
+
end
|
75
|
+
|
76
|
+
if (e.class==Mysql2::Error)
|
77
|
+
print "Can't connect to mysql with current configuration, please review provided credentials. Please execute again this command to reconfigure"
|
78
|
+
DB::DBManager.delete_config_file(@env.data_dir)
|
79
|
+
end
|
80
|
+
|
81
|
+
# #if (exception.class==Mysql2::Error)
|
82
|
+
# if (i<1)
|
83
|
+
# puts "Configuring database connection "
|
84
|
+
# puts "Do you let us create "
|
85
|
+
# puts "Insert privilege database user:"
|
86
|
+
# user=STDIN.noecho(&:gets).chomp
|
87
|
+
# print "\n"
|
88
|
+
# print "Insert privilege database password:"
|
89
|
+
# password=STDIN.noecho(&:gets).chomp
|
90
|
+
# print "\n"
|
91
|
+
# print "Insert database name:"
|
92
|
+
# database=STDIN.noecho(&:gets).chomp
|
93
|
+
# print "\n"
|
94
|
+
# DB::DBManager.create_config_file(@env.data_dir,'localhost',database,user,password)
|
95
|
+
# i=i+1
|
96
|
+
# retry
|
97
|
+
# end
|
52
98
|
end
|
53
99
|
|
54
|
-
if (
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
100
|
+
if (!db.nil?)
|
101
|
+
#Checking if user knows the old password
|
102
|
+
if (db.node_password_set? && !db.node_default_password_set?)
|
103
|
+
print "Insert current password: "
|
104
|
+
old_password=STDIN.noecho(&:gets).chomp
|
105
|
+
print "\n"
|
106
|
+
if !db.node_check_password?(old_password)
|
107
|
+
@env.ui.error("Password failed!")
|
108
|
+
return 0
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
pass_m = "Insert your new password for this Node: "
|
113
|
+
confirm_m = "Please Insert again the new password: "
|
114
|
+
|
115
|
+
|
116
|
+
if STDIN.respond_to?(:noecho)
|
117
|
+
print pass_m
|
118
|
+
password=STDIN.noecho(&:gets).chomp
|
119
|
+
print "\n#{confirm_m}"
|
120
|
+
confirm=STDIN.noecho(&:gets).chomp
|
121
|
+
print "\n"
|
122
|
+
else
|
123
|
+
#FIXME Don't show password
|
124
|
+
password = @env.ui.ask(pass_m)
|
125
|
+
confirm = @env.ui.ask(confirm_m)
|
126
|
+
end
|
127
|
+
|
128
|
+
if (password==confirm)
|
129
|
+
db.node_password_set(password)
|
130
|
+
@env.ui.success("Password changed!")
|
131
|
+
else
|
132
|
+
@env.ui.error("Passwords does not match!")
|
133
|
+
end
|
59
134
|
end
|
60
135
|
|
61
136
|
|
@@ -20,12 +20,13 @@ module Vagrant
|
|
20
20
|
return if !argv
|
21
21
|
raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if argv.length > 1
|
22
22
|
|
23
|
-
begin
|
24
|
-
ServerAPI::ServerManager.run(File.dirname(@env.lock_path),@env.data_dir,argv[0].to_i)
|
23
|
+
begin
|
24
|
+
#ServerAPI::ServerManager.run(File.dirname(@env.lock_path),@env.data_dir,@env,argv[0].to_i)
|
25
|
+
ServerAPI::ServerManager.run(@env.tmp_path,@env.data_dir,@env,argv[0].to_i)
|
25
26
|
rescue Exception => e
|
26
27
|
@env.ui.error(e.message)
|
27
28
|
end
|
28
|
-
|
29
|
+
|
29
30
|
0
|
30
31
|
end
|
31
32
|
|
@@ -20,7 +20,8 @@ module Vagrant
|
|
20
20
|
raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if argv.length > 1
|
21
21
|
|
22
22
|
begin
|
23
|
-
ServerAPI::ServerManager.stop(File.dirname(@env.lock_path),@env.data_dir)
|
23
|
+
#ServerAPI::ServerManager.stop(File.dirname(@env.lock_path),@env.data_dir)
|
24
|
+
ServerAPI::ServerManager.stop(@env.tmp_path,@env.data_dir)
|
24
25
|
rescue Exception => e
|
25
26
|
@env.ui.error(e.message)
|
26
27
|
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'vagrant'
|
2
|
+
require 'vagrant-node/dbmanager'
|
3
|
+
require 'vagrant-node/pwmanager'
|
4
|
+
require 'vagrant-node/exceptions.rb'
|
5
|
+
require 'singleton'
|
6
|
+
|
7
|
+
|
8
|
+
module Vagrant
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
module Vagrant
|
13
|
+
module Config
|
14
|
+
class Loader
|
15
|
+
def procs_for_path(path)
|
16
|
+
|
17
|
+
return Config.capture_configures do
|
18
|
+
begin
|
19
|
+
|
20
|
+
#Module hack to remove previous constans definitions
|
21
|
+
modaux=Module.new
|
22
|
+
res=modaux.module_eval(File.read(path))
|
23
|
+
obconstants = Object.constants
|
24
|
+
modaux.constants.each do |var|
|
25
|
+
Object.send(:remove_const,var) if Object.constants.include?(var)
|
26
|
+
end
|
27
|
+
############################33
|
28
|
+
|
29
|
+
|
30
|
+
Kernel.load path
|
31
|
+
rescue SyntaxError => e
|
32
|
+
# Report syntax errors in a nice way.
|
33
|
+
raise Errors::VagrantfileSyntaxError, :file => e.message
|
34
|
+
rescue SystemExit
|
35
|
+
# Continue raising that exception...
|
36
|
+
raise
|
37
|
+
rescue Vagrant::Errors::VagrantError
|
38
|
+
# Continue raising known Vagrant errors since they already
|
39
|
+
# contain well worded error messages and context.
|
40
|
+
raise
|
41
|
+
rescue Exception => e
|
42
|
+
@logger.error("Vagrantfile load error: #{e.message}")
|
43
|
+
@logger.error(e.backtrace.join("\n"))
|
44
|
+
|
45
|
+
# Report the generic exception
|
46
|
+
raise Errors::VagrantfileLoadError,
|
47
|
+
:path => path,
|
48
|
+
:message => e.message
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# class Environment
|
57
|
+
|
58
|
+
# def reload
|
59
|
+
|
60
|
+
# @config_global = nil
|
61
|
+
# @config_loader = nil
|
62
|
+
# @config_global = config_global
|
63
|
+
|
64
|
+
|
65
|
+
# @config_global
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
|
69
|
+
module Node
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
class ObManager
|
74
|
+
|
75
|
+
include Singleton
|
76
|
+
|
77
|
+
def initialize
|
78
|
+
@env = Environment.new
|
79
|
+
@db = DB::DBManager.new(@env.data_dir) if (!@db)
|
80
|
+
@pw = PwManager.new(@db) if (!@pw)
|
81
|
+
end
|
82
|
+
|
83
|
+
def env
|
84
|
+
if (!@env)
|
85
|
+
self.env=Environment.new
|
86
|
+
end
|
87
|
+
@env
|
88
|
+
end
|
89
|
+
|
90
|
+
def reload_env
|
91
|
+
|
92
|
+
if (@env)
|
93
|
+
@env.unload if (@env)
|
94
|
+
# @env.reload
|
95
|
+
@env = nil
|
96
|
+
end
|
97
|
+
|
98
|
+
@env = Environment.new
|
99
|
+
self.env=@env
|
100
|
+
|
101
|
+
@env
|
102
|
+
end
|
103
|
+
|
104
|
+
def env=(environment)
|
105
|
+
@env=environment
|
106
|
+
@db=nil
|
107
|
+
@pw=nil
|
108
|
+
@db = DB::DBManager.new(@env.data_dir) if (!@db)
|
109
|
+
@pw = PwManager.new(@db) if (!@pw)
|
110
|
+
end
|
111
|
+
|
112
|
+
def dbmanager
|
113
|
+
@db
|
114
|
+
end
|
115
|
+
|
116
|
+
def pwmanager
|
117
|
+
@pw
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/vagrant-node/server.rb
CHANGED
@@ -16,12 +16,12 @@ module Vagrant
|
|
16
16
|
DEFAULT_BIND_PORT = 3333
|
17
17
|
BIND_ADDRESS = "0.0.0.0"
|
18
18
|
LOG_FILE = "webrick.log"
|
19
|
-
def self.run(pid_path,data_path,port=DEFAULT_BIND_PORT)
|
19
|
+
def self.run(pid_path,data_path,env,port=DEFAULT_BIND_PORT)
|
20
20
|
|
21
|
-
check_password(data_path)
|
21
|
+
check_password(data_path)
|
22
22
|
|
23
23
|
pid_file = File.join(pid_path,PIDFILENAME)
|
24
|
-
|
24
|
+
|
25
25
|
pid = fork do
|
26
26
|
|
27
27
|
|
@@ -42,12 +42,25 @@ module Vagrant
|
|
42
42
|
:AccessLog => access_log
|
43
43
|
}
|
44
44
|
|
45
|
+
|
45
46
|
#begin
|
46
47
|
server = WEBrick::HTTPServer.new(options)
|
47
48
|
|
48
49
|
server.mount "/", Rack::Handler::WEBrick,ServerAPI::API.new
|
49
|
-
|
50
|
-
|
50
|
+
|
51
|
+
trap("INT") { server.shutdown }
|
52
|
+
|
53
|
+
trap("USR1") {
|
54
|
+
|
55
|
+
#Stopping server
|
56
|
+
server.shutdown
|
57
|
+
|
58
|
+
#Restarting server
|
59
|
+
server = WEBrick::HTTPServer.new(options)
|
60
|
+
server.mount "/", Rack::Handler::WEBrick,ServerAPI::API.new
|
61
|
+
server.start
|
62
|
+
}
|
63
|
+
|
51
64
|
PidFile.create(pid_file,Process.pid)
|
52
65
|
|
53
66
|
server.start
|
@@ -93,24 +106,17 @@ module Vagrant
|
|
93
106
|
end
|
94
107
|
|
95
108
|
private
|
109
|
+
|
96
110
|
|
97
111
|
def self.check_password(data_path)
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
# print "\n"
|
107
|
-
# end
|
108
|
-
# end
|
109
|
-
|
110
|
-
# if(!@db.node_check_password?(password))
|
111
|
-
# raise "Password failed!"
|
112
|
-
# end
|
113
|
-
end
|
112
|
+
|
113
|
+
begin
|
114
|
+
@db = DB::DBManager.new(data_path)
|
115
|
+
rescue
|
116
|
+
if (!@db || !@db.node_password_set? || @db.node_default_password_set?)
|
117
|
+
raise "Please, set first a password with the command \"vagrant nodeserver passwd\""
|
118
|
+
end
|
119
|
+
end
|
114
120
|
true
|
115
121
|
end
|
116
122
|
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require "vagrant/util/busy"
|
3
|
+
|
4
|
+
require "vagrant/util/subprocess"
|
5
|
+
|
6
|
+
module Vagrant
|
7
|
+
module Node
|
8
|
+
module Util
|
9
|
+
# This class downloads files using various protocols by subprocessing
|
10
|
+
# to cURL. cURL is a much more capable and complete download tool than
|
11
|
+
# a hand-rolled Ruby library, so we defer to it's expertise.
|
12
|
+
class Downloader
|
13
|
+
UPDATETIME = 10
|
14
|
+
|
15
|
+
def initialize(source, destination, options=nil)
|
16
|
+
@source = source.to_s
|
17
|
+
@destination = destination.to_s
|
18
|
+
|
19
|
+
# Get the various optional values
|
20
|
+
options ||= {}
|
21
|
+
#@callback = options[:callback]
|
22
|
+
@insecure = options[:insecure]
|
23
|
+
@ui = options[:ui]
|
24
|
+
@db = options[:db]
|
25
|
+
@box_name = options[:box_name]
|
26
|
+
end
|
27
|
+
|
28
|
+
# This executes the actual download, downloading the source file
|
29
|
+
# to the destination with the given opens used to initialize this
|
30
|
+
# class.
|
31
|
+
#
|
32
|
+
# If this method returns without an exception, the download
|
33
|
+
# succeeded. An exception will be raised if the download failed.
|
34
|
+
def download!
|
35
|
+
# Build the list of parameters to execute with cURL
|
36
|
+
options = [
|
37
|
+
"--fail",
|
38
|
+
"--location",
|
39
|
+
"--max-redirs", "10",
|
40
|
+
"--output", @destination
|
41
|
+
]
|
42
|
+
|
43
|
+
options << "--insecure" if @insecure
|
44
|
+
options << @source
|
45
|
+
|
46
|
+
# Specify some options for the subprocess
|
47
|
+
subprocess_options = {}
|
48
|
+
|
49
|
+
# If we're in Vagrant, then we use the packaged CA bundle
|
50
|
+
if Vagrant.in_installer?
|
51
|
+
subprocess_options[:env] ||= {}
|
52
|
+
subprocess_options[:env]["CURL_CA_BUNDLE"] =
|
53
|
+
File.expand_path("cacert.pem", ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"])
|
54
|
+
end
|
55
|
+
|
56
|
+
# This variable can contain the proc that'll be sent to
|
57
|
+
# the subprocess execute.
|
58
|
+
data_proc = nil
|
59
|
+
|
60
|
+
subprocess_options[:notify] = :stderr
|
61
|
+
|
62
|
+
progress_data = ""
|
63
|
+
progress_regexp = /(\r(.+?))\r/
|
64
|
+
|
65
|
+
# Setup the proc that'll receive the real-time data from
|
66
|
+
# the downloader.
|
67
|
+
last_id=@db.add_box_download_info(@box_name,@source)
|
68
|
+
|
69
|
+
comienzo = Time.now();
|
70
|
+
|
71
|
+
data_proc = Proc.new do |type, data|
|
72
|
+
# Type will always be "stderr" because that is the only
|
73
|
+
# type of data we're subscribed for notifications.
|
74
|
+
|
75
|
+
# Accumulate progress_data
|
76
|
+
progress_data << data
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
while true
|
81
|
+
# If we have a full amount of column data (two "\r") then
|
82
|
+
# we report new progress reports. Otherwise, just keep
|
83
|
+
# accumulating.
|
84
|
+
match = progress_regexp.match(progress_data)
|
85
|
+
break if !match
|
86
|
+
data = match[2]
|
87
|
+
progress_data.gsub!(match[1], "")
|
88
|
+
|
89
|
+
# Ignore the first \r and split by whitespace to grab the columns
|
90
|
+
columns = data.strip.split(/\s+/)
|
91
|
+
|
92
|
+
# COLUMN DATA:
|
93
|
+
#
|
94
|
+
# 0 - % total
|
95
|
+
# 1 - Total size
|
96
|
+
# 2 - % received
|
97
|
+
# 3 - Received size
|
98
|
+
# 4 - % transferred
|
99
|
+
# 5 - Transferred size
|
100
|
+
# 6 - Average download speed
|
101
|
+
# 7 - Average upload speed
|
102
|
+
# 9 - Total time
|
103
|
+
# 9 - Time spent
|
104
|
+
# 10 - Time left
|
105
|
+
# 11 - Current speed
|
106
|
+
|
107
|
+
if (Time.now()-comienzo > UPDATETIME)
|
108
|
+
@db.update_box_download_info(last_id,"#{columns[0]}%","#{columns[10]}")
|
109
|
+
comienzo = Time.now()
|
110
|
+
end
|
111
|
+
|
112
|
+
#db.execute("INSERT INTO #{PASSWORD_TABLE} VALUES (\"#{DEFAULT_NODE_PASSWORD}\");");
|
113
|
+
#pp "Progress: #{columns[0]}% (Rate: #{columns[11]}/s, Estimated time remaining: #{columns[10]})"
|
114
|
+
|
115
|
+
#output = "Progress: #{columns[0]}% (Rate: #{columns[11]}/s, Estimated time remaining: #{columns[10]})"
|
116
|
+
##@ui.clear_line
|
117
|
+
#@ui.info(output, :new_line => false)
|
118
|
+
end
|
119
|
+
|
120
|
+
#@db.update_box_download_info(last_id,"100%","--:--:--")
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
# Add the subprocess options onto the options we'll execute with
|
125
|
+
options << subprocess_options
|
126
|
+
|
127
|
+
# Create the callback that is called if we are interrupted
|
128
|
+
interrupted = false
|
129
|
+
int_callback = Proc.new do
|
130
|
+
interrupted = true
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
# Execute!
|
135
|
+
result = Vagrant::Util::Busy.busy(int_callback) do
|
136
|
+
Vagrant::Util::Subprocess.execute("curl", *options, &data_proc)
|
137
|
+
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
if ((!interrupted) && (result.exit_code==0))
|
143
|
+
# @db.update_box_download_info(last_id,"100%","--:--:--")
|
144
|
+
@db.delete_box_download(last_id)
|
145
|
+
else
|
146
|
+
@db.set_box_download_error(last_id)
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
# If the download was interrupted, then raise a specific error
|
151
|
+
raise Errors::DownloaderInterrupted if interrupted
|
152
|
+
|
153
|
+
|
154
|
+
# If we're outputting to the UI, clear the output to
|
155
|
+
# avoid lingering progress meters.
|
156
|
+
@ui.clear_line if @ui
|
157
|
+
|
158
|
+
|
159
|
+
# If it didn't exit successfully, we need to parse the data and
|
160
|
+
# show an error message.
|
161
|
+
if result.exit_code != 0
|
162
|
+
|
163
|
+
@logger.warn("Downloader exit code: #{result.exit_code}") if @logger
|
164
|
+
|
165
|
+
parts = result.stderr.split(/\n*curl:\s+\(\d+\)\s*/, 2)
|
166
|
+
|
167
|
+
parts[1] ||= ""
|
168
|
+
|
169
|
+
|
170
|
+
|
171
|
+
raise Errors::DownloaderError, :message => parts[1].chomp
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
|
176
|
+
# Everything succeeded
|
177
|
+
true
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|