nitro 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +8 -0
- data/ChangeLog +1546 -0
- data/LICENCE +32 -0
- data/README +278 -0
- data/RELEASES +7 -0
- data/Rakefile +79 -0
- data/bin/cluster.rb +219 -0
- data/doc/architecture.txt +28 -0
- data/doc/bugs.txt +7 -0
- data/doc/css.txt +20 -0
- data/doc/ideas.txt +120 -0
- data/doc/pg.txt +47 -0
- data/doc/svn.txt +82 -0
- data/doc/todo.txt +30 -0
- data/etc/new-project.rb +18 -0
- data/examples/simple/README +15 -0
- data/examples/simple/app.rb +31 -0
- data/examples/simple/conf/apache.conf +100 -0
- data/examples/simple/conf/config.rb +89 -0
- data/examples/simple/conf/debug-config.rb +53 -0
- data/examples/simple/conf/live-config.rb +48 -0
- data/examples/simple/conf/overrides.rb +9 -0
- data/examples/simple/conf/requires.rb +51 -0
- data/examples/simple/ctl +32 -0
- data/examples/simple/env.rb +33 -0
- data/examples/simple/install.rb +12 -0
- data/examples/simple/lib/articles/entities.rb +35 -0
- data/examples/simple/lib/articles/lc-en.rb +36 -0
- data/examples/simple/lib/articles/methods.rb +55 -0
- data/examples/simple/lib/articles/part.rb +58 -0
- data/examples/simple/logs/access_log +2 -0
- data/examples/simple/logs/apache.log +3 -0
- data/examples/simple/logs/app.log +1 -0
- data/examples/simple/logs/events.log +1 -0
- data/examples/simple/root/add-article.sx +15 -0
- data/examples/simple/root/article-form.ss +20 -0
- data/examples/simple/root/comments-form.ss +16 -0
- data/examples/simple/root/comments.si +30 -0
- data/examples/simple/root/index.sx +44 -0
- data/examples/simple/root/shader/shader.xsl +100 -0
- data/examples/simple/root/shader/style.css +9 -0
- data/examples/simple/root/view-article.sx +30 -0
- data/examples/tiny/app.rb +30 -0
- data/examples/tiny/conf/apache.conf +100 -0
- data/examples/tiny/conf/config.rb +67 -0
- data/examples/tiny/conf/requires.rb +40 -0
- data/examples/tiny/ctl +31 -0
- data/examples/tiny/logs/access_log +9 -0
- data/examples/tiny/logs/apache.log +9 -0
- data/examples/tiny/root/index.sx +35 -0
- data/lib/n/app/cluster.rb +219 -0
- data/lib/n/app/cookie.rb +86 -0
- data/lib/n/app/filters/autologin.rb +50 -0
- data/lib/n/app/fragment.rb +67 -0
- data/lib/n/app/handlers.rb +120 -0
- data/lib/n/app/handlers/code-handler.rb +184 -0
- data/lib/n/app/handlers/page-handler.rb +612 -0
- data/lib/n/app/request-part.rb +59 -0
- data/lib/n/app/request.rb +653 -0
- data/lib/n/app/script.rb +398 -0
- data/lib/n/app/server.rb +53 -0
- data/lib/n/app/session.rb +224 -0
- data/lib/n/app/user.rb +47 -0
- data/lib/n/app/webrick-servlet.rb +213 -0
- data/lib/n/app/webrick.rb +70 -0
- data/lib/n/application.rb +187 -0
- data/lib/n/config.rb +31 -0
- data/lib/n/db.rb +217 -0
- data/lib/n/db/README +232 -0
- data/lib/n/db/connection.rb +369 -0
- data/lib/n/db/make-release.sh +26 -0
- data/lib/n/db/managed.rb +235 -0
- data/lib/n/db/mixins.rb +282 -0
- data/lib/n/db/mysql.rb +342 -0
- data/lib/n/db/psql.rb +378 -0
- data/lib/n/db/tools.rb +110 -0
- data/lib/n/db/utils.rb +99 -0
- data/lib/n/events.rb +118 -0
- data/lib/n/l10n.rb +22 -0
- data/lib/n/logger.rb +33 -0
- data/lib/n/macros.rb +53 -0
- data/lib/n/mixins.rb +46 -0
- data/lib/n/parts.rb +154 -0
- data/lib/n/properties.rb +194 -0
- data/lib/n/server.rb +61 -0
- data/lib/n/server/PLAYBACK.txt +8 -0
- data/lib/n/server/RESEARCH.txt +13 -0
- data/lib/n/server/filter.rb +77 -0
- data/lib/n/shaders.rb +167 -0
- data/lib/n/sitemap.rb +188 -0
- data/lib/n/std.rb +69 -0
- data/lib/n/sync/clc.rb +108 -0
- data/lib/n/sync/handler.rb +221 -0
- data/lib/n/sync/server.rb +170 -0
- data/lib/n/tools/README +11 -0
- data/lib/n/ui/date-select.rb +74 -0
- data/lib/n/ui/pager.rb +187 -0
- data/lib/n/ui/popup.rb +45 -0
- data/lib/n/ui/select.rb +41 -0
- data/lib/n/ui/tabs.rb +34 -0
- data/lib/n/utils/array.rb +92 -0
- data/lib/n/utils/cache.rb +144 -0
- data/lib/n/utils/gfx.rb +108 -0
- data/lib/n/utils/hash.rb +148 -0
- data/lib/n/utils/html.rb +147 -0
- data/lib/n/utils/http.rb +98 -0
- data/lib/n/utils/mail.rb +28 -0
- data/lib/n/utils/number.rb +31 -0
- data/lib/n/utils/pool.rb +66 -0
- data/lib/n/utils/string.rb +297 -0
- data/lib/n/utils/template.rb +38 -0
- data/lib/n/utils/time.rb +91 -0
- data/lib/n/utils/uri.rb +193 -0
- data/lib/xsl/base.xsl +205 -0
- data/lib/xsl/ce.xsl +30 -0
- data/lib/xsl/localization.xsl +23 -0
- data/lib/xsl/xforms.xsl +26 -0
- data/test/run.rb +95 -0
- metadata +187 -0
@@ -0,0 +1,221 @@
|
|
1
|
+
# = Synchronous Handler
|
2
|
+
#
|
3
|
+
# === Design:
|
4
|
+
#
|
5
|
+
# A handler can act as a multiplexer to implement multiple services
|
6
|
+
# per server.
|
7
|
+
#
|
8
|
+
# code: tml, drak
|
9
|
+
#
|
10
|
+
# (c) 2004 Navel, all rights reserved.
|
11
|
+
# $Id: handler.rb 71 2004-10-18 10:50:22Z gmosx $
|
12
|
+
|
13
|
+
require "timeout"
|
14
|
+
require "n/server"
|
15
|
+
|
16
|
+
module N; module Sync
|
17
|
+
|
18
|
+
class HandlerExitException < Exception; end
|
19
|
+
|
20
|
+
# = Worker
|
21
|
+
#
|
22
|
+
# Override this to create your worker.
|
23
|
+
#
|
24
|
+
class Handler
|
25
|
+
SEPARATOR = "\000"
|
26
|
+
|
27
|
+
# socket timeout in seconds
|
28
|
+
@@socket_timeout = 30
|
29
|
+
|
30
|
+
# handler timeout in seconds
|
31
|
+
@@handler_timeout = 60 * 60
|
32
|
+
|
33
|
+
attr :server, :socket
|
34
|
+
|
35
|
+
attr :thread, :touch_time
|
36
|
+
|
37
|
+
# status
|
38
|
+
attr_accessor :status
|
39
|
+
|
40
|
+
STATUS_IDLE = 0
|
41
|
+
STATUS_RUNNING = 10
|
42
|
+
STATUS_STOPPED = 20
|
43
|
+
|
44
|
+
def initialize(server, socket)
|
45
|
+
@server, @socket = server, socket
|
46
|
+
end
|
47
|
+
|
48
|
+
def start
|
49
|
+
touch!
|
50
|
+
@thread = Thread.new(&method("run").to_proc)
|
51
|
+
end
|
52
|
+
|
53
|
+
def stop
|
54
|
+
unless STATUS_STOPPED == @status
|
55
|
+
@status = STATUS_STOPPED
|
56
|
+
$log.debug "Stoping handler" if $DBG
|
57
|
+
|
58
|
+
begin
|
59
|
+
@socket.close()
|
60
|
+
rescue Exception, StandardError => e
|
61
|
+
# gmosx: this is needed to be FAULT TOLERANT
|
62
|
+
# DRINK IT!
|
63
|
+
$log.error "Cannot close exception when stoping handler."
|
64
|
+
end
|
65
|
+
|
66
|
+
# gmosx: why not? more FAULT TOLERANT.
|
67
|
+
@server.handlers.delete_if {|h| STATUS_STOPPED == h.status}
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# called by the garbage collector.
|
73
|
+
#
|
74
|
+
def gc!
|
75
|
+
@thread.raise HandlerExitException.new()
|
76
|
+
end
|
77
|
+
|
78
|
+
def run
|
79
|
+
@status = STATUS_RUNNING
|
80
|
+
|
81
|
+
while (STATUS_RUNNING == @status) and (not @socket.closed?)
|
82
|
+
begin
|
83
|
+
unless cmd = read()
|
84
|
+
$log.error "Client closed connection"
|
85
|
+
@status = STATUS_IDLE
|
86
|
+
else
|
87
|
+
touch!
|
88
|
+
handle(cmd.chop())
|
89
|
+
end
|
90
|
+
rescue HandlerExitException => ex
|
91
|
+
@status = STATUS_IDLE
|
92
|
+
$log.debug "Handler exit" if $DBG
|
93
|
+
rescue Exception, StandardError => ex
|
94
|
+
@status = STATUS_IDLE
|
95
|
+
$log.debug ex if $DBG
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
begin
|
100
|
+
stop()
|
101
|
+
rescue Exception, StandardError => e
|
102
|
+
# gmosx: this rescue block needed to get debug info!
|
103
|
+
#
|
104
|
+
$log.error pp_exception(e)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def touch!
|
109
|
+
@touch_time = Time.now()
|
110
|
+
end
|
111
|
+
|
112
|
+
def live?
|
113
|
+
return Time.now < @touch_time + @@handler_timeout
|
114
|
+
end
|
115
|
+
|
116
|
+
# Read xml from the socket
|
117
|
+
#
|
118
|
+
def read
|
119
|
+
return unless @status == STATUS_RUNNING
|
120
|
+
|
121
|
+
cmd = nil
|
122
|
+
|
123
|
+
# gmosx: no timeout here!
|
124
|
+
cmd = @socket.gets(SEPARATOR)
|
125
|
+
$log.debug "in: #{cmd}" if $DBG
|
126
|
+
|
127
|
+
return cmd
|
128
|
+
end
|
129
|
+
|
130
|
+
# Write xml to the socket
|
131
|
+
#
|
132
|
+
def write(cmd)
|
133
|
+
return unless @status == STATUS_RUNNING
|
134
|
+
|
135
|
+
$log.debug "out: #{cmd}" if $DBG
|
136
|
+
|
137
|
+
begin
|
138
|
+
timeout(@@socket_timeout) { @socket << "#{cmd}#{SEPARATOR}" }
|
139
|
+
rescue Exception, StandardError => e
|
140
|
+
# gmosx: the socket is invalid close the handler.
|
141
|
+
$log.error pp_exception(e)
|
142
|
+
stop()
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# parse the xml commnad to create a ruby method call.
|
147
|
+
#
|
148
|
+
def parse(cmd)
|
149
|
+
return unless @status == STATUS_RUNNING
|
150
|
+
|
151
|
+
cmd = REXML::Document.new(cmd).root()
|
152
|
+
|
153
|
+
method = cmd.name().gsub(/-/, "_")
|
154
|
+
unless cmd.attributes.empty?
|
155
|
+
args = "(#{cmd.attributes.collect { |k, v| ":#{k.gsub(/-/, "_")} => '#{v}'" }.join(", ")})"
|
156
|
+
else
|
157
|
+
args = nil
|
158
|
+
end
|
159
|
+
|
160
|
+
return "cmd_#{method}#{args}"
|
161
|
+
end
|
162
|
+
|
163
|
+
# Generic handler.
|
164
|
+
#
|
165
|
+
def handle(cmd)
|
166
|
+
if cmd = parse(cmd)
|
167
|
+
$log.debug "exec: #{cmd}" if $DBG
|
168
|
+
begin
|
169
|
+
eval(cmd)
|
170
|
+
rescue => ex
|
171
|
+
$log.error ex
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Send to another handler
|
177
|
+
#
|
178
|
+
def send(cmd, handler)
|
179
|
+
handler.write(cmd)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Broadcast to all handlers (clients)
|
183
|
+
#
|
184
|
+
def broadcast(cmd)
|
185
|
+
@server.broadcast(cmd)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Broadcast to all other handlers (clients)
|
189
|
+
#
|
190
|
+
def broadcast_to_others(cmd)
|
191
|
+
for handler in @server.handlers
|
192
|
+
unless self == handler
|
193
|
+
handler.write(cmd)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# ------------------------------------------------------------------
|
199
|
+
|
200
|
+
def info(txt)
|
201
|
+
write(%|<info text="#{txt}" />|)
|
202
|
+
end
|
203
|
+
|
204
|
+
def error(txt)
|
205
|
+
write(%|<error text="#{txt}" />|)
|
206
|
+
end
|
207
|
+
|
208
|
+
# ------------------------------------------------------------------
|
209
|
+
# Commands
|
210
|
+
|
211
|
+
def cmd_ping(args)
|
212
|
+
cmd = %{<pong time="#{Time.now}"}
|
213
|
+
cmd += %{ challenge="#{args[:challenge]}"} if args
|
214
|
+
cmd += " />"
|
215
|
+
|
216
|
+
write(cmd)
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
end; end #modules
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# = Syncrhronous Server
|
2
|
+
#
|
3
|
+
# === Design:
|
4
|
+
#
|
5
|
+
# Should support clustered operation: A cluster of servers is spawned.
|
6
|
+
# The client connects to one of the servers at random. The client sticks
|
7
|
+
# to the server. Each server considers the others as clients and
|
8
|
+
# rebroadcasts all the events.
|
9
|
+
#
|
10
|
+
# We keep one service per server for simplicity, if we need multiple
|
11
|
+
# services we can implement a multiplexer service (handler).
|
12
|
+
#
|
13
|
+
# === TODO:
|
14
|
+
#
|
15
|
+
# - Investigate if this server can be done with select.
|
16
|
+
#
|
17
|
+
# code: gmosx, drak
|
18
|
+
#
|
19
|
+
# (c) 2004 Navel, all rights reserved.
|
20
|
+
# $Id: server.rb 71 2004-10-18 10:50:22Z gmosx $
|
21
|
+
|
22
|
+
require "thread"
|
23
|
+
require "socket"
|
24
|
+
require "rexml/document"
|
25
|
+
|
26
|
+
require "n/std"
|
27
|
+
require "n/application"
|
28
|
+
require "n/utils/array"
|
29
|
+
require "n/sync/handler"
|
30
|
+
|
31
|
+
module N; module Sync
|
32
|
+
|
33
|
+
# = Server
|
34
|
+
#
|
35
|
+
# A Synchronous server tha communicates with clients using
|
36
|
+
# application specific xml protocols. Typically communicates with
|
37
|
+
# Flash clients utilising the XmlSocket functionality to enable 'push'
|
38
|
+
# flash applications.
|
39
|
+
#
|
40
|
+
# The Macromedia XML Socket protocol is used, \000 is used as a eof
|
41
|
+
# marker. NO: the XML protocol is not really appropriate, better use
|
42
|
+
# a space optimized delimited ASCII protocol.
|
43
|
+
#
|
44
|
+
class Server < N::Application
|
45
|
+
MONITOR_INTERVAL = 2 * 60
|
46
|
+
|
47
|
+
# a single tcp server accepts all tcp requests
|
48
|
+
attr :tcp_server
|
49
|
+
|
50
|
+
# the listening address/port for this server.
|
51
|
+
attr_reader :address, :port
|
52
|
+
|
53
|
+
# the handler class, used to instantiate handlers
|
54
|
+
attr :handler_class
|
55
|
+
|
56
|
+
# maximum number of clients to connect.
|
57
|
+
attr_accessor :max_handlers
|
58
|
+
|
59
|
+
# the handler list, all handler attached to the server.
|
60
|
+
attr_accessor :handlers
|
61
|
+
|
62
|
+
# status
|
63
|
+
attr_accessor :status
|
64
|
+
|
65
|
+
STATUS_IDLE = 0
|
66
|
+
STATUS_RUNNING = 10
|
67
|
+
STATUS_STOPPED = 20
|
68
|
+
|
69
|
+
def initialize(address = "localhost", port = 2121, handler_class = N::Sync::Handler, max_handlers = nil)
|
70
|
+
@address, @port = address, port
|
71
|
+
@max_handlers = max_handlers
|
72
|
+
@name, @title = "nsync", "Navel Sync"
|
73
|
+
@status = STATUS_IDLE
|
74
|
+
|
75
|
+
@handlers = N::SafeArray.new()
|
76
|
+
@handler_class = handler_class
|
77
|
+
|
78
|
+
super()
|
79
|
+
end
|
80
|
+
|
81
|
+
def start()
|
82
|
+
# a single tcp server accepts all tcp requests
|
83
|
+
@tcp_server = TCPServer.new(address, port)
|
84
|
+
|
85
|
+
start_monitor()
|
86
|
+
run()
|
87
|
+
end
|
88
|
+
|
89
|
+
def stop()
|
90
|
+
@status = STATUS_STOPED
|
91
|
+
end
|
92
|
+
|
93
|
+
def run()
|
94
|
+
@status = STATUS_RUNNING
|
95
|
+
$log.info "Server is running."
|
96
|
+
|
97
|
+
begin
|
98
|
+
while (STATUS_RUNNING == @status)
|
99
|
+
socket = @tcp_server.accept()
|
100
|
+
$log.debug "Socket accepted" if $DBG
|
101
|
+
handler = @handler_class.new(self, socket)
|
102
|
+
handlers << handler
|
103
|
+
handler.start()
|
104
|
+
end
|
105
|
+
rescue Exception, StandardError => e
|
106
|
+
$log.error pp_exception(e)
|
107
|
+
# tml, FIXME:
|
108
|
+
# this is dangerous! make more FAULT TOLERANT!
|
109
|
+
end
|
110
|
+
|
111
|
+
@status = STATUS_IDLE
|
112
|
+
end
|
113
|
+
|
114
|
+
def shutdown()
|
115
|
+
end_monitor()
|
116
|
+
for handler in handlers
|
117
|
+
handler.stop()
|
118
|
+
end
|
119
|
+
Thread.exit()
|
120
|
+
end
|
121
|
+
|
122
|
+
# ------------------------------------------------------------------
|
123
|
+
|
124
|
+
def start_monitor
|
125
|
+
@monitor = Thread.new {
|
126
|
+
begin
|
127
|
+
while true
|
128
|
+
sleep(MONITOR_INTERVAL)
|
129
|
+
$log.debug "monitor beat -- #{@handlers.size()} handlers" if $DBG
|
130
|
+
|
131
|
+
for handler in @handlers
|
132
|
+
unless handler.live?
|
133
|
+
$log.info "Idle handler detected!"
|
134
|
+
handler.gc!()
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
rescue Exception, StandardError => e
|
139
|
+
$log.error pp_exception(e)
|
140
|
+
end
|
141
|
+
}
|
142
|
+
end
|
143
|
+
|
144
|
+
def end_monitor
|
145
|
+
Thread.kill(@monitor)
|
146
|
+
end
|
147
|
+
|
148
|
+
# ------------------------------------------------------------------
|
149
|
+
|
150
|
+
def send(cmd, handler)
|
151
|
+
handler.write(cmd)
|
152
|
+
end
|
153
|
+
|
154
|
+
def broadcast(cmd)
|
155
|
+
for handler in @handlers
|
156
|
+
handler.write(cmd)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
if $0 == __FILE__
|
163
|
+
require "n/logger"
|
164
|
+
|
165
|
+
$log = Logger.new(STDERR)
|
166
|
+
Server.new().exec()
|
167
|
+
end
|
168
|
+
|
169
|
+
end; end #modules
|
170
|
+
|
data/lib/n/tools/README
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
= Software Enginnering Tools
|
2
|
+
|
3
|
+
This directory contains standalone tools typically used in
|
4
|
+
software engineering and configuration, system administration,
|
5
|
+
or network management.
|
6
|
+
|
7
|
+
=== TODO:
|
8
|
+
|
9
|
+
- tool to replace all $xxx variables with english names.
|
10
|
+
|
11
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# = Date Select
|
2
|
+
#
|
3
|
+
# code:
|
4
|
+
# George Moschovitis <gm@navel.gr>
|
5
|
+
#
|
6
|
+
# (c) 2004 Navel, all rights reserved.
|
7
|
+
# $Id: select.rb 39 2004-09-29 12:40:53Z elathan $
|
8
|
+
|
9
|
+
module N; module UI
|
10
|
+
|
11
|
+
MONTHS_EN = %w{ - January February March April May June July August September October November December }
|
12
|
+
MONTHS_EL = %w{ - ���������� ����������� ������� �������� ����� ������� ������� ��������� ����������� ��������� ��������� ���������� }
|
13
|
+
|
14
|
+
# Date select.
|
15
|
+
#
|
16
|
+
def self.date_select(prefix, day, month, year, yorder = 0, locale = "en")
|
17
|
+
str = ""
|
18
|
+
months = ("en" == locale ? MONTHS_EN : MONTHS_EL)
|
19
|
+
|
20
|
+
str << %{
|
21
|
+
<select name="#{prefix}day">
|
22
|
+
<option value="0">--</option>
|
23
|
+
}
|
24
|
+
|
25
|
+
for i in (1..31)
|
26
|
+
if i == day
|
27
|
+
str << %{<option value="#{i}" selected="1">#{i}</option>}
|
28
|
+
else
|
29
|
+
str << %{<option value="#{i}">#{i}</option>}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
str << %{
|
34
|
+
</select>
|
35
|
+
|
36
|
+
<select name="#{prefix}month">
|
37
|
+
<option value="0">-------</option>
|
38
|
+
}
|
39
|
+
|
40
|
+
for i in (1..12)
|
41
|
+
if i == month
|
42
|
+
str << %{<option value="#{i}" selected="1">#{months[i]}</option>}
|
43
|
+
else
|
44
|
+
str << %{<option value="#{i}">#{months[i]}</option>}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
str << %{
|
49
|
+
</select>
|
50
|
+
|
51
|
+
<select name="#{prefix}year">
|
52
|
+
<option value="0">--</option>
|
53
|
+
}
|
54
|
+
|
55
|
+
nowyear = Time.now.year
|
56
|
+
|
57
|
+
for i in (0..60)
|
58
|
+
y = yorder < 0 ? nowyear - i : nowyear + i
|
59
|
+
if y == year
|
60
|
+
str << %{<option value="#{y}" selected="1">#{y}</option>}
|
61
|
+
else
|
62
|
+
str << %{<option value="#{y}">#{y}</option>}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
str << %{
|
67
|
+
</select>
|
68
|
+
}
|
69
|
+
|
70
|
+
return str
|
71
|
+
end
|
72
|
+
|
73
|
+
end; end # module
|
74
|
+
|