stormy-cloud 0.0.8
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.
- data/dashboard/dash.rb +62 -0
- data/dashboard/index.html +124 -0
- data/dashboard/public/images/apple-touch-icon-114x114.png +0 -0
- data/dashboard/public/images/apple-touch-icon-72x72.png +0 -0
- data/dashboard/public/images/apple-touch-icon.png +0 -0
- data/dashboard/public/images/favicon.ico +0 -0
- data/dashboard/public/jquery.js +4 -0
- data/dashboard/public/stylesheets/base.css +269 -0
- data/dashboard/public/stylesheets/layout.css +58 -0
- data/dashboard/public/stylesheets/skeleton.css +242 -0
- data/lib/stormy-cloud.rb +174 -0
- data/lib/transport.rb +269 -0
- data/lib/transports/tcp.rb +40 -0
- metadata +58 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Skeleton V1.2
|
|
3
|
+
* Copyright 2011, Dave Gamache
|
|
4
|
+
* www.getskeleton.com
|
|
5
|
+
* Free to use under the MIT license.
|
|
6
|
+
* http://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
* 6/20/2012
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/* Table of Content
|
|
11
|
+
==================================================
|
|
12
|
+
#Site Styles
|
|
13
|
+
#Page Styles
|
|
14
|
+
#Media Queries
|
|
15
|
+
#Font-Face */
|
|
16
|
+
|
|
17
|
+
/* #Site Styles
|
|
18
|
+
================================================== */
|
|
19
|
+
|
|
20
|
+
/* #Page Styles
|
|
21
|
+
================================================== */
|
|
22
|
+
|
|
23
|
+
/* #Media Queries
|
|
24
|
+
================================================== */
|
|
25
|
+
|
|
26
|
+
/* Smaller than standard 960 (devices and browsers) */
|
|
27
|
+
@media only screen and (max-width: 959px) {}
|
|
28
|
+
|
|
29
|
+
/* Tablet Portrait size to standard 960 (devices and browsers) */
|
|
30
|
+
@media only screen and (min-width: 768px) and (max-width: 959px) {}
|
|
31
|
+
|
|
32
|
+
/* All Mobile Sizes (devices and browser) */
|
|
33
|
+
@media only screen and (max-width: 767px) {}
|
|
34
|
+
|
|
35
|
+
/* Mobile Landscape Size to Tablet Portrait (devices and browsers) */
|
|
36
|
+
@media only screen and (min-width: 480px) and (max-width: 767px) {}
|
|
37
|
+
|
|
38
|
+
/* Mobile Portrait Size to Mobile Landscape Size (devices and browsers) */
|
|
39
|
+
@media only screen and (max-width: 479px) {}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
/* #Font-Face
|
|
43
|
+
================================================== */
|
|
44
|
+
/* This is the proper syntax for an @font-face file
|
|
45
|
+
Just create a "fonts" folder at the root,
|
|
46
|
+
copy your FontName into code below and remove
|
|
47
|
+
comment brackets */
|
|
48
|
+
|
|
49
|
+
/* @font-face {
|
|
50
|
+
font-family: 'FontName';
|
|
51
|
+
src: url('../fonts/FontName.eot');
|
|
52
|
+
src: url('../fonts/FontName.eot?iefix') format('eot'),
|
|
53
|
+
url('../fonts/FontName.woff') format('woff'),
|
|
54
|
+
url('../fonts/FontName.ttf') format('truetype'),
|
|
55
|
+
url('../fonts/FontName.svg#webfontZam02nTh') format('svg');
|
|
56
|
+
font-weight: normal;
|
|
57
|
+
font-style: normal; }
|
|
58
|
+
*/
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Skeleton V1.2
|
|
3
|
+
* Copyright 2011, Dave Gamache
|
|
4
|
+
* www.getskeleton.com
|
|
5
|
+
* Free to use under the MIT license.
|
|
6
|
+
* http://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
* 6/20/2012
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/* Table of Contents
|
|
12
|
+
==================================================
|
|
13
|
+
#Base 960 Grid
|
|
14
|
+
#Tablet (Portrait)
|
|
15
|
+
#Mobile (Portrait)
|
|
16
|
+
#Mobile (Landscape)
|
|
17
|
+
#Clearing */
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/* #Base 960 Grid
|
|
22
|
+
================================================== */
|
|
23
|
+
|
|
24
|
+
.container { position: relative; width: 960px; margin: 0 auto; padding: 0; }
|
|
25
|
+
.container .column,
|
|
26
|
+
.container .columns { float: left; display: inline; margin-left: 10px; margin-right: 10px; }
|
|
27
|
+
.row { margin-bottom: 20px; }
|
|
28
|
+
|
|
29
|
+
/* Nested Column Classes */
|
|
30
|
+
.column.alpha, .columns.alpha { margin-left: 0; }
|
|
31
|
+
.column.omega, .columns.omega { margin-right: 0; }
|
|
32
|
+
|
|
33
|
+
/* Base Grid */
|
|
34
|
+
.container .one.column,
|
|
35
|
+
.container .one.columns { width: 40px; }
|
|
36
|
+
.container .two.columns { width: 100px; }
|
|
37
|
+
.container .three.columns { width: 160px; }
|
|
38
|
+
.container .four.columns { width: 220px; }
|
|
39
|
+
.container .five.columns { width: 280px; }
|
|
40
|
+
.container .six.columns { width: 340px; }
|
|
41
|
+
.container .seven.columns { width: 400px; }
|
|
42
|
+
.container .eight.columns { width: 460px; }
|
|
43
|
+
.container .nine.columns { width: 520px; }
|
|
44
|
+
.container .ten.columns { width: 580px; }
|
|
45
|
+
.container .eleven.columns { width: 640px; }
|
|
46
|
+
.container .twelve.columns { width: 700px; }
|
|
47
|
+
.container .thirteen.columns { width: 760px; }
|
|
48
|
+
.container .fourteen.columns { width: 820px; }
|
|
49
|
+
.container .fifteen.columns { width: 880px; }
|
|
50
|
+
.container .sixteen.columns { width: 940px; }
|
|
51
|
+
|
|
52
|
+
.container .one-third.column { width: 300px; }
|
|
53
|
+
.container .two-thirds.column { width: 620px; }
|
|
54
|
+
|
|
55
|
+
/* Offsets */
|
|
56
|
+
.container .offset-by-one { padding-left: 60px; }
|
|
57
|
+
.container .offset-by-two { padding-left: 120px; }
|
|
58
|
+
.container .offset-by-three { padding-left: 180px; }
|
|
59
|
+
.container .offset-by-four { padding-left: 240px; }
|
|
60
|
+
.container .offset-by-five { padding-left: 300px; }
|
|
61
|
+
.container .offset-by-six { padding-left: 360px; }
|
|
62
|
+
.container .offset-by-seven { padding-left: 420px; }
|
|
63
|
+
.container .offset-by-eight { padding-left: 480px; }
|
|
64
|
+
.container .offset-by-nine { padding-left: 540px; }
|
|
65
|
+
.container .offset-by-ten { padding-left: 600px; }
|
|
66
|
+
.container .offset-by-eleven { padding-left: 660px; }
|
|
67
|
+
.container .offset-by-twelve { padding-left: 720px; }
|
|
68
|
+
.container .offset-by-thirteen { padding-left: 780px; }
|
|
69
|
+
.container .offset-by-fourteen { padding-left: 840px; }
|
|
70
|
+
.container .offset-by-fifteen { padding-left: 900px; }
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
/* #Tablet (Portrait)
|
|
75
|
+
================================================== */
|
|
76
|
+
|
|
77
|
+
/* Note: Design for a width of 768px */
|
|
78
|
+
|
|
79
|
+
@media only screen and (min-width: 768px) and (max-width: 959px) {
|
|
80
|
+
.container { width: 768px; }
|
|
81
|
+
.container .column,
|
|
82
|
+
.container .columns { margin-left: 10px; margin-right: 10px; }
|
|
83
|
+
.column.alpha, .columns.alpha { margin-left: 0; margin-right: 10px; }
|
|
84
|
+
.column.omega, .columns.omega { margin-right: 0; margin-left: 10px; }
|
|
85
|
+
.alpha.omega { margin-left: 0; margin-right: 0; }
|
|
86
|
+
|
|
87
|
+
.container .one.column,
|
|
88
|
+
.container .one.columns { width: 28px; }
|
|
89
|
+
.container .two.columns { width: 76px; }
|
|
90
|
+
.container .three.columns { width: 124px; }
|
|
91
|
+
.container .four.columns { width: 172px; }
|
|
92
|
+
.container .five.columns { width: 220px; }
|
|
93
|
+
.container .six.columns { width: 268px; }
|
|
94
|
+
.container .seven.columns { width: 316px; }
|
|
95
|
+
.container .eight.columns { width: 364px; }
|
|
96
|
+
.container .nine.columns { width: 412px; }
|
|
97
|
+
.container .ten.columns { width: 460px; }
|
|
98
|
+
.container .eleven.columns { width: 508px; }
|
|
99
|
+
.container .twelve.columns { width: 556px; }
|
|
100
|
+
.container .thirteen.columns { width: 604px; }
|
|
101
|
+
.container .fourteen.columns { width: 652px; }
|
|
102
|
+
.container .fifteen.columns { width: 700px; }
|
|
103
|
+
.container .sixteen.columns { width: 748px; }
|
|
104
|
+
|
|
105
|
+
.container .one-third.column { width: 236px; }
|
|
106
|
+
.container .two-thirds.column { width: 492px; }
|
|
107
|
+
|
|
108
|
+
/* Offsets */
|
|
109
|
+
.container .offset-by-one { padding-left: 48px; }
|
|
110
|
+
.container .offset-by-two { padding-left: 96px; }
|
|
111
|
+
.container .offset-by-three { padding-left: 144px; }
|
|
112
|
+
.container .offset-by-four { padding-left: 192px; }
|
|
113
|
+
.container .offset-by-five { padding-left: 240px; }
|
|
114
|
+
.container .offset-by-six { padding-left: 288px; }
|
|
115
|
+
.container .offset-by-seven { padding-left: 336px; }
|
|
116
|
+
.container .offset-by-eight { padding-left: 384px; }
|
|
117
|
+
.container .offset-by-nine { padding-left: 432px; }
|
|
118
|
+
.container .offset-by-ten { padding-left: 480px; }
|
|
119
|
+
.container .offset-by-eleven { padding-left: 528px; }
|
|
120
|
+
.container .offset-by-twelve { padding-left: 576px; }
|
|
121
|
+
.container .offset-by-thirteen { padding-left: 624px; }
|
|
122
|
+
.container .offset-by-fourteen { padding-left: 672px; }
|
|
123
|
+
.container .offset-by-fifteen { padding-left: 720px; }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
/* #Mobile (Portrait)
|
|
128
|
+
================================================== */
|
|
129
|
+
|
|
130
|
+
/* Note: Design for a width of 320px */
|
|
131
|
+
|
|
132
|
+
@media only screen and (max-width: 767px) {
|
|
133
|
+
.container { width: 300px; }
|
|
134
|
+
.container .columns,
|
|
135
|
+
.container .column { margin: 0; }
|
|
136
|
+
|
|
137
|
+
.container .one.column,
|
|
138
|
+
.container .one.columns,
|
|
139
|
+
.container .two.columns,
|
|
140
|
+
.container .three.columns,
|
|
141
|
+
.container .four.columns,
|
|
142
|
+
.container .five.columns,
|
|
143
|
+
.container .six.columns,
|
|
144
|
+
.container .seven.columns,
|
|
145
|
+
.container .eight.columns,
|
|
146
|
+
.container .nine.columns,
|
|
147
|
+
.container .ten.columns,
|
|
148
|
+
.container .eleven.columns,
|
|
149
|
+
.container .twelve.columns,
|
|
150
|
+
.container .thirteen.columns,
|
|
151
|
+
.container .fourteen.columns,
|
|
152
|
+
.container .fifteen.columns,
|
|
153
|
+
.container .sixteen.columns,
|
|
154
|
+
.container .one-third.column,
|
|
155
|
+
.container .two-thirds.column { width: 300px; }
|
|
156
|
+
|
|
157
|
+
/* Offsets */
|
|
158
|
+
.container .offset-by-one,
|
|
159
|
+
.container .offset-by-two,
|
|
160
|
+
.container .offset-by-three,
|
|
161
|
+
.container .offset-by-four,
|
|
162
|
+
.container .offset-by-five,
|
|
163
|
+
.container .offset-by-six,
|
|
164
|
+
.container .offset-by-seven,
|
|
165
|
+
.container .offset-by-eight,
|
|
166
|
+
.container .offset-by-nine,
|
|
167
|
+
.container .offset-by-ten,
|
|
168
|
+
.container .offset-by-eleven,
|
|
169
|
+
.container .offset-by-twelve,
|
|
170
|
+
.container .offset-by-thirteen,
|
|
171
|
+
.container .offset-by-fourteen,
|
|
172
|
+
.container .offset-by-fifteen { padding-left: 0; }
|
|
173
|
+
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
/* #Mobile (Landscape)
|
|
178
|
+
================================================== */
|
|
179
|
+
|
|
180
|
+
/* Note: Design for a width of 480px */
|
|
181
|
+
|
|
182
|
+
@media only screen and (min-width: 480px) and (max-width: 767px) {
|
|
183
|
+
.container { width: 420px; }
|
|
184
|
+
.container .columns,
|
|
185
|
+
.container .column { margin: 0; }
|
|
186
|
+
|
|
187
|
+
.container .one.column,
|
|
188
|
+
.container .one.columns,
|
|
189
|
+
.container .two.columns,
|
|
190
|
+
.container .three.columns,
|
|
191
|
+
.container .four.columns,
|
|
192
|
+
.container .five.columns,
|
|
193
|
+
.container .six.columns,
|
|
194
|
+
.container .seven.columns,
|
|
195
|
+
.container .eight.columns,
|
|
196
|
+
.container .nine.columns,
|
|
197
|
+
.container .ten.columns,
|
|
198
|
+
.container .eleven.columns,
|
|
199
|
+
.container .twelve.columns,
|
|
200
|
+
.container .thirteen.columns,
|
|
201
|
+
.container .fourteen.columns,
|
|
202
|
+
.container .fifteen.columns,
|
|
203
|
+
.container .sixteen.columns,
|
|
204
|
+
.container .one-third.column,
|
|
205
|
+
.container .two-thirds.column { width: 420px; }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
/* #Clearing
|
|
210
|
+
================================================== */
|
|
211
|
+
|
|
212
|
+
/* Self Clearing Goodness */
|
|
213
|
+
.container:after { content: "\0020"; display: block; height: 0; clear: both; visibility: hidden; }
|
|
214
|
+
|
|
215
|
+
/* Use clearfix class on parent to clear nested columns,
|
|
216
|
+
or wrap each row of columns in a <div class="row"> */
|
|
217
|
+
.clearfix:before,
|
|
218
|
+
.clearfix:after,
|
|
219
|
+
.row:before,
|
|
220
|
+
.row:after {
|
|
221
|
+
content: '\0020';
|
|
222
|
+
display: block;
|
|
223
|
+
overflow: hidden;
|
|
224
|
+
visibility: hidden;
|
|
225
|
+
width: 0;
|
|
226
|
+
height: 0; }
|
|
227
|
+
.row:after,
|
|
228
|
+
.clearfix:after {
|
|
229
|
+
clear: both; }
|
|
230
|
+
.row,
|
|
231
|
+
.clearfix {
|
|
232
|
+
zoom: 1; }
|
|
233
|
+
|
|
234
|
+
/* You can also use a <br class="clear" /> to clear columns */
|
|
235
|
+
.clear {
|
|
236
|
+
clear: both;
|
|
237
|
+
display: block;
|
|
238
|
+
overflow: hidden;
|
|
239
|
+
visibility: hidden;
|
|
240
|
+
width: 0;
|
|
241
|
+
height: 0;
|
|
242
|
+
}
|
data/lib/stormy-cloud.rb
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
require 'thread'
|
|
2
|
+
['tcp'].each do |t|
|
|
3
|
+
require_relative "./transports/#{t}.rb"
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class StormyCloud
|
|
7
|
+
attr_reader :result, :name, :server
|
|
8
|
+
|
|
9
|
+
def initialize(name, server, transport=StormyCloudTCPTransport)
|
|
10
|
+
@name = name
|
|
11
|
+
@server = server
|
|
12
|
+
@result = nil
|
|
13
|
+
@config = {
|
|
14
|
+
:wait => 15,
|
|
15
|
+
:debug => false,
|
|
16
|
+
:port => 4312
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@split = lambda do
|
|
20
|
+
raise NotImplementedError.new("split was not specified")
|
|
21
|
+
end
|
|
22
|
+
@map = lambda do |t|
|
|
23
|
+
raise NotImplementedError.new("map was not specified")
|
|
24
|
+
end
|
|
25
|
+
@reduce = lambda do |t, r|
|
|
26
|
+
raise NotImplementedError.new("reduce was not specified")
|
|
27
|
+
end
|
|
28
|
+
@finally = lambda { nil }
|
|
29
|
+
|
|
30
|
+
@reduce_mutex = Mutex.new
|
|
31
|
+
|
|
32
|
+
@transport_class = transport
|
|
33
|
+
|
|
34
|
+
if block_given?
|
|
35
|
+
yield self
|
|
36
|
+
run
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# If the key is not in a whitelist, raise an ArgumentError. Otherwise,
|
|
41
|
+
# check whether a value has been given. If no value has been given, return
|
|
42
|
+
# the value of that key. If a value has been given, check whether it is a
|
|
43
|
+
# `kind_of` the corresponding class for that key, and if so set the
|
|
44
|
+
# configuration variable to the new value. Otherwise raise an ArgumentError.
|
|
45
|
+
def config(key, value=nil)
|
|
46
|
+
_validate_config(key, value)
|
|
47
|
+
|
|
48
|
+
if value.nil?
|
|
49
|
+
@config[key]
|
|
50
|
+
else
|
|
51
|
+
@config[key] = value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Validate the configuration keys and values.
|
|
57
|
+
def _validate_config(key, value)
|
|
58
|
+
valid = {
|
|
59
|
+
:wait => Fixnum,
|
|
60
|
+
:debug => [TrueClass, FalseClass],
|
|
61
|
+
:port => Fixnum
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if not valid.keys.include? key
|
|
65
|
+
raise ArgumentError.new("invalid configuration key: #{key.to_s}")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
if not value.nil?
|
|
69
|
+
type = valid[key]
|
|
70
|
+
error = false
|
|
71
|
+
|
|
72
|
+
if type.kind_of? Array
|
|
73
|
+
error = true unless type.any? {|t| value.kind_of? t }
|
|
74
|
+
else
|
|
75
|
+
error = true unless value.kind_of? type
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
if error
|
|
79
|
+
raise ArgumentError.new("invalid configuration value for #{key.to_s}")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# When called with a block, save that block for later usage. When called
|
|
85
|
+
# without a block, use the block saved earlier to generate a list of tasks.
|
|
86
|
+
# Raise a TypeError if the block doesn't return an array.
|
|
87
|
+
def split(&block)
|
|
88
|
+
if block
|
|
89
|
+
@split = block
|
|
90
|
+
else
|
|
91
|
+
_split
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Actually call the block set using the split method. Check whether the
|
|
96
|
+
# value generated is actually an array before returning it, if it isn't
|
|
97
|
+
# raise a TypeError.
|
|
98
|
+
def _split
|
|
99
|
+
tasks = @split.call
|
|
100
|
+
if tasks.kind_of? Array
|
|
101
|
+
tasks
|
|
102
|
+
else
|
|
103
|
+
raise TypeError, "split should return an array"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# When called with a block, save the block for later user. When called
|
|
108
|
+
# with a single argument, call the block saved earlier. Raise an
|
|
109
|
+
# ArgumentError if called without a block and task.
|
|
110
|
+
def map(task=nil, &block)
|
|
111
|
+
if task.nil? and block.nil?
|
|
112
|
+
raise ArgumentError, "map called without a task and block"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
if block
|
|
116
|
+
@map = block
|
|
117
|
+
else
|
|
118
|
+
@map.call(task)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# When called with a block, save the block for later user. When called
|
|
123
|
+
# with a single argument, call the block saved earlier. Raise an
|
|
124
|
+
# ArgumentError if called without a block and task.
|
|
125
|
+
def reduce(task=nil, result=nil, &block)
|
|
126
|
+
if block.nil? and (task.nil? or result.nil?)
|
|
127
|
+
raise ArgumentError, "reduce called without a result and block"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
if block
|
|
131
|
+
@reduce = block
|
|
132
|
+
else
|
|
133
|
+
@reduce_mutex.synchronize do
|
|
134
|
+
@reduce.call(task, result)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# When called with a block, save it for later use. Otherwise, call the
|
|
140
|
+
# block which was saved earlier.
|
|
141
|
+
def finally(&block)
|
|
142
|
+
if block
|
|
143
|
+
@finally = block
|
|
144
|
+
else
|
|
145
|
+
@result = @finally.call
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Run the job!
|
|
150
|
+
def run
|
|
151
|
+
if config(:debug)
|
|
152
|
+
split.each {|t| reduce(t, map(t)) }
|
|
153
|
+
@result = finally
|
|
154
|
+
else
|
|
155
|
+
if ['node', 'server'].include? ARGV[0]
|
|
156
|
+
@transport = @transport_class.new(self)
|
|
157
|
+
@transport.mode = ARGV[0].to_sym
|
|
158
|
+
puts "> My identifier is #{@transport.identifier}."
|
|
159
|
+
puts "> Running in #{@transport.mode} mode."
|
|
160
|
+
puts ">"
|
|
161
|
+
if @transport.mode == :server
|
|
162
|
+
Thread.new { @transport.run }
|
|
163
|
+
$transport = @transport
|
|
164
|
+
require_relative '../dashboard/dash.rb'
|
|
165
|
+
else
|
|
166
|
+
@transport.run
|
|
167
|
+
end
|
|
168
|
+
else
|
|
169
|
+
puts "ARGV[0] should be the run mode (node or server)."
|
|
170
|
+
exit
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|