ar_mailer_aws 0.0.4 → 0.1.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 +3 -0
- data/CHANGELOG.md +50 -0
- data/README.md +2 -1
- data/ar_mailer_aws.gemspec +5 -2
- data/bin/ar_mailer_aws +3 -3
- data/lib/ar_mailer_aws.rb +35 -87
- data/lib/ar_mailer_aws/clients/amazon_ses.rb +49 -0
- data/lib/ar_mailer_aws/clients/base.rb +117 -0
- data/lib/ar_mailer_aws/clients/mandrill.rb +53 -0
- data/lib/ar_mailer_aws/clients/smtp.rb +45 -0
- data/lib/ar_mailer_aws/options_parser.rb +107 -0
- data/lib/ar_mailer_aws/version.rb +1 -1
- data/lib/generators/ar_mailer_aws/templates/ar_mailer_aws.rb +42 -2
- data/lib/generators/ar_mailer_aws/templates/migration.rb +1 -0
- data/spec/ar_mailer_aws/ar_mailer_aws_spec.rb +42 -5
- data/spec/ar_mailer_aws/clients/amazon_ses_spec.rb +96 -0
- data/spec/ar_mailer_aws/clients/base_spec.rb +91 -0
- data/spec/ar_mailer_aws/clients/smtp_spec.rb +38 -0
- data/spec/ar_mailer_aws/mailer_spec.rb +7 -7
- data/spec/ar_mailer_aws/options_parser_spec.rb +51 -0
- data/spec/spec_helper.rb +1 -0
- metadata +74 -36
- data/lib/ar_mailer_aws/sender.rb +0 -90
- data/spec/ar_mailer_aws/parse_options_spec.rb +0 -44
- data/spec/ar_mailer_aws/sender_spec.rb +0 -158
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ArMailerAWS::Clients::SMTP do
|
4
|
+
|
5
|
+
describe '#send_emails' do
|
6
|
+
before do
|
7
|
+
allow(ArMailerAWS).to receive(:client_config).and_return({smtp: {
|
8
|
+
address: 'smtp.example.com',
|
9
|
+
port: 587,
|
10
|
+
domain: 'example.com',
|
11
|
+
authentication: 'plain',
|
12
|
+
enable_starttls_auto: true,
|
13
|
+
user_name: 'test@example.com',
|
14
|
+
password: '123456'
|
15
|
+
}})
|
16
|
+
@client = ArMailerAWS::Clients::SMTP.new
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'start smtp session' do
|
20
|
+
session = Net::SMTP.new('smtp.example.com', 587)
|
21
|
+
expect(Net::SMTP).to receive(:new).with('smtp.example.com', 587).and_return(session)
|
22
|
+
expect(session).to receive(:start).with('example.com', 'test@example.com', '123456', 'plain')
|
23
|
+
@client.send_emails([])
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'send emails' do
|
27
|
+
session = double('session').as_null_object
|
28
|
+
email = double('email', id: 1, mail: 'mail', from: 'from', to: 'to')
|
29
|
+
|
30
|
+
allow(Net::SMTP).to receive(:new).and_return(session)
|
31
|
+
allow(session).to receive(:start).and_yield(session)
|
32
|
+
expect(session).to receive(:send_message).with('mail', 'from', 'to')
|
33
|
+
expect(email).to receive(:destroy)
|
34
|
+
@client.send_emails([email])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -4,23 +4,23 @@ describe ArMailerAWS::Mailer do
|
|
4
4
|
|
5
5
|
it 'initializer email_class option' do
|
6
6
|
mailer = ArMailerAWS::Mailer.new(email_class: CustomEmailClass)
|
7
|
-
mailer.email_class.name.
|
7
|
+
expect(mailer.email_class.name).to eq 'CustomEmailClass'
|
8
8
|
end
|
9
9
|
|
10
10
|
context 'delivering' do
|
11
11
|
before do
|
12
12
|
@mail = double('Mail')
|
13
|
-
@mail.
|
14
|
-
@mail.
|
15
|
-
@mail.
|
13
|
+
allow(@mail).to receive(:return_path).and_return('from@example.com')
|
14
|
+
allow(@mail).to receive(:destinations).and_return(['to@example.com'])
|
15
|
+
allow(@mail).to receive(:encoded).and_return('email content')
|
16
16
|
@mailer = ArMailerAWS::Mailer.new
|
17
17
|
end
|
18
18
|
|
19
19
|
it '#check_params' do
|
20
20
|
params = @mailer.send(:check_params, @mail)
|
21
|
-
params[0].
|
22
|
-
params[1].
|
23
|
-
params[2].
|
21
|
+
expect(params[0]).to eq 'from@example.com'
|
22
|
+
expect(params[1]).to eq ['to@example.com']
|
23
|
+
expect(params[2]).to eq 'email content'
|
24
24
|
end
|
25
25
|
|
26
26
|
it 'store emails into db on deliver!' do
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ArMailerAWS::OptionsParser do
|
4
|
+
|
5
|
+
it 'return defaults if no options specified' do
|
6
|
+
options = ArMailerAWS::OptionsParser.parse_options([])
|
7
|
+
expect(options.batch_size).to eq 100
|
8
|
+
expect(options.delay).to eq 180
|
9
|
+
expect(options.quota).to eq 10_000
|
10
|
+
expect(options.rate).to eq 5
|
11
|
+
expect(options.max_age).to eq 3600 * 24 * 7
|
12
|
+
expect(options.max_attempts).to eq 5
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'batch_size' do
|
16
|
+
expect(ArMailerAWS::OptionsParser.parse_options(%w(-b 10)).batch_size).to eq 10
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'delay' do
|
20
|
+
expect(ArMailerAWS::OptionsParser.parse_options(%w(-d 90)).delay).to eq 90
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'quota' do
|
24
|
+
expect(ArMailerAWS::OptionsParser.parse_options(%w(-q 100)).quota).to eq 100
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'rate' do
|
28
|
+
expect(ArMailerAWS::OptionsParser.parse_options(%w(-r 7)).rate).to eq 7
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'max_age' do
|
32
|
+
expect(ArMailerAWS::OptionsParser.parse_options(%w(-m 300)).max_age).to eq 300
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'max_attempts' do
|
36
|
+
expect(ArMailerAWS::OptionsParser.parse_options(%w(-a 10)).max_attempts).to eq 10
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'verbose' do
|
40
|
+
expect(ArMailerAWS::OptionsParser.parse_options(%w(-v)).verbose).to be_truthy
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'pid_dir' do
|
44
|
+
expect(ArMailerAWS::OptionsParser.parse_options(%w(-p tmp/pids)).pid_dir).to eq 'tmp/pids'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'app_name' do
|
48
|
+
expect(ArMailerAWS::OptionsParser.parse_options(%w(--app-name my_daemon)).app_name).to eq 'my_daemon'
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,139 +1,167 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ar_mailer_aws
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Leschenko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-12-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: daemons
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 1.1.9
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 1.1.9
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: aws-sdk
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
|
-
- - ~>
|
45
|
+
- - "~>"
|
32
46
|
- !ruby/object:Gem::Version
|
33
47
|
version: '1.0'
|
34
|
-
type: :
|
48
|
+
type: :development
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
|
-
- - ~>
|
52
|
+
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '1.0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
56
|
+
name: mandrill-api
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
|
-
- -
|
59
|
+
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
48
|
-
type: :
|
61
|
+
version: 1.0.49
|
62
|
+
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
|
-
- -
|
66
|
+
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
68
|
+
version: 1.0.49
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mail
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.5.4
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.5.4
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: bundler
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
58
86
|
requirements:
|
59
|
-
- - ~>
|
87
|
+
- - "~>"
|
60
88
|
- !ruby/object:Gem::Version
|
61
89
|
version: '1.3'
|
62
90
|
type: :development
|
63
91
|
prerelease: false
|
64
92
|
version_requirements: !ruby/object:Gem::Requirement
|
65
93
|
requirements:
|
66
|
-
- - ~>
|
94
|
+
- - "~>"
|
67
95
|
- !ruby/object:Gem::Version
|
68
96
|
version: '1.3'
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
name: rake
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
72
100
|
requirements:
|
73
|
-
- -
|
101
|
+
- - ">="
|
74
102
|
- !ruby/object:Gem::Version
|
75
103
|
version: '0'
|
76
104
|
type: :development
|
77
105
|
prerelease: false
|
78
106
|
version_requirements: !ruby/object:Gem::Requirement
|
79
107
|
requirements:
|
80
|
-
- -
|
108
|
+
- - ">="
|
81
109
|
- !ruby/object:Gem::Version
|
82
110
|
version: '0'
|
83
111
|
- !ruby/object:Gem::Dependency
|
84
112
|
name: rspec
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|
86
114
|
requirements:
|
87
|
-
- -
|
115
|
+
- - ">="
|
88
116
|
- !ruby/object:Gem::Version
|
89
117
|
version: '0'
|
90
118
|
type: :development
|
91
119
|
prerelease: false
|
92
120
|
version_requirements: !ruby/object:Gem::Requirement
|
93
121
|
requirements:
|
94
|
-
- -
|
122
|
+
- - ">="
|
95
123
|
- !ruby/object:Gem::Version
|
96
124
|
version: '0'
|
97
125
|
- !ruby/object:Gem::Dependency
|
98
126
|
name: forgery
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
100
128
|
requirements:
|
101
|
-
- -
|
129
|
+
- - ">="
|
102
130
|
- !ruby/object:Gem::Version
|
103
131
|
version: '0'
|
104
132
|
type: :development
|
105
133
|
prerelease: false
|
106
134
|
version_requirements: !ruby/object:Gem::Requirement
|
107
135
|
requirements:
|
108
|
-
- -
|
136
|
+
- - ">="
|
109
137
|
- !ruby/object:Gem::Version
|
110
138
|
version: '0'
|
111
139
|
- !ruby/object:Gem::Dependency
|
112
140
|
name: sqlite3
|
113
141
|
requirement: !ruby/object:Gem::Requirement
|
114
142
|
requirements:
|
115
|
-
- -
|
143
|
+
- - ">="
|
116
144
|
- !ruby/object:Gem::Version
|
117
145
|
version: '0'
|
118
146
|
type: :development
|
119
147
|
prerelease: false
|
120
148
|
version_requirements: !ruby/object:Gem::Requirement
|
121
149
|
requirements:
|
122
|
-
- -
|
150
|
+
- - ">="
|
123
151
|
- !ruby/object:Gem::Version
|
124
152
|
version: '0'
|
125
153
|
- !ruby/object:Gem::Dependency
|
126
154
|
name: activerecord
|
127
155
|
requirement: !ruby/object:Gem::Requirement
|
128
156
|
requirements:
|
129
|
-
- -
|
157
|
+
- - ">="
|
130
158
|
- !ruby/object:Gem::Version
|
131
159
|
version: '3.0'
|
132
160
|
type: :development
|
133
161
|
prerelease: false
|
134
162
|
version_requirements: !ruby/object:Gem::Requirement
|
135
163
|
requirements:
|
136
|
-
- -
|
164
|
+
- - ">="
|
137
165
|
- !ruby/object:Gem::Version
|
138
166
|
version: '3.0'
|
139
167
|
description: Daemon for sending butches of emails via Amazon SES using ActiveRecord
|
@@ -145,8 +173,9 @@ executables:
|
|
145
173
|
extensions: []
|
146
174
|
extra_rdoc_files: []
|
147
175
|
files:
|
148
|
-
- .gitignore
|
149
|
-
- .travis.yml
|
176
|
+
- ".gitignore"
|
177
|
+
- ".travis.yml"
|
178
|
+
- CHANGELOG.md
|
150
179
|
- Gemfile
|
151
180
|
- LICENSE.txt
|
152
181
|
- README.md
|
@@ -154,19 +183,26 @@ files:
|
|
154
183
|
- ar_mailer_aws.gemspec
|
155
184
|
- bin/ar_mailer_aws
|
156
185
|
- lib/ar_mailer_aws.rb
|
186
|
+
- lib/ar_mailer_aws/clients/amazon_ses.rb
|
187
|
+
- lib/ar_mailer_aws/clients/base.rb
|
188
|
+
- lib/ar_mailer_aws/clients/mandrill.rb
|
189
|
+
- lib/ar_mailer_aws/clients/smtp.rb
|
157
190
|
- lib/ar_mailer_aws/mailer.rb
|
191
|
+
- lib/ar_mailer_aws/options_parser.rb
|
158
192
|
- lib/ar_mailer_aws/railtie.rb
|
159
|
-
- lib/ar_mailer_aws/sender.rb
|
160
193
|
- lib/ar_mailer_aws/version.rb
|
161
194
|
- lib/generators/ar_mailer_aws/ar_mailer_aws_generator.rb
|
162
195
|
- lib/generators/ar_mailer_aws/templates/ar_mailer_aws.rb
|
163
196
|
- lib/generators/ar_mailer_aws/templates/migration.rb
|
164
197
|
- lib/generators/ar_mailer_aws/templates/model.rb
|
165
198
|
- spec/ar_mailer_aws/ar_mailer_aws_spec.rb
|
199
|
+
- spec/ar_mailer_aws/clients/amazon_ses_spec.rb
|
200
|
+
- spec/ar_mailer_aws/clients/base_spec.rb
|
201
|
+
- spec/ar_mailer_aws/clients/smtp_spec.rb
|
166
202
|
- spec/ar_mailer_aws/mailer_spec.rb
|
167
|
-
- spec/ar_mailer_aws/
|
168
|
-
- spec/ar_mailer_aws/sender_spec.rb
|
203
|
+
- spec/ar_mailer_aws/options_parser_spec.rb
|
169
204
|
- spec/spec_helper.rb
|
205
|
+
- unused/sender.rb
|
170
206
|
homepage: https://github.com/leschenko/ar_mailer_aws
|
171
207
|
licenses:
|
172
208
|
- MIT
|
@@ -177,23 +213,25 @@ require_paths:
|
|
177
213
|
- lib
|
178
214
|
required_ruby_version: !ruby/object:Gem::Requirement
|
179
215
|
requirements:
|
180
|
-
- -
|
216
|
+
- - ">="
|
181
217
|
- !ruby/object:Gem::Version
|
182
218
|
version: '0'
|
183
219
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
184
220
|
requirements:
|
185
|
-
- -
|
221
|
+
- - ">="
|
186
222
|
- !ruby/object:Gem::Version
|
187
223
|
version: '0'
|
188
224
|
requirements: []
|
189
225
|
rubyforge_project:
|
190
|
-
rubygems_version: 2.
|
226
|
+
rubygems_version: 2.4.3
|
191
227
|
signing_key:
|
192
228
|
specification_version: 4
|
193
|
-
summary: Send
|
229
|
+
summary: Send batches of emails via Amazon SES
|
194
230
|
test_files:
|
195
231
|
- spec/ar_mailer_aws/ar_mailer_aws_spec.rb
|
232
|
+
- spec/ar_mailer_aws/clients/amazon_ses_spec.rb
|
233
|
+
- spec/ar_mailer_aws/clients/base_spec.rb
|
234
|
+
- spec/ar_mailer_aws/clients/smtp_spec.rb
|
196
235
|
- spec/ar_mailer_aws/mailer_spec.rb
|
197
|
-
- spec/ar_mailer_aws/
|
198
|
-
- spec/ar_mailer_aws/sender_spec.rb
|
236
|
+
- spec/ar_mailer_aws/options_parser_spec.rb
|
199
237
|
- spec/spec_helper.rb
|
data/lib/ar_mailer_aws/sender.rb
DELETED
@@ -1,90 +0,0 @@
|
|
1
|
-
module ArMailerAWS
|
2
|
-
class Sender
|
3
|
-
attr_reader :options, :model, :ses
|
4
|
-
|
5
|
-
def initialize(options={})
|
6
|
-
@options = options.is_a?(Hash) ? OpenStruct.new(options) : options
|
7
|
-
@model = ArMailerAWS.email_class.constantize
|
8
|
-
@ses = AWS::SimpleEmailService.new ArMailerAWS.ses_options
|
9
|
-
@day = Date.today
|
10
|
-
@sent_count = 0
|
11
|
-
end
|
12
|
-
|
13
|
-
def send_batch
|
14
|
-
cleanup
|
15
|
-
emails = find_emails
|
16
|
-
log "found #{emails.length} emails to deliver"
|
17
|
-
send_emails(emails) unless emails.empty?
|
18
|
-
end
|
19
|
-
|
20
|
-
def send_emails(emails)
|
21
|
-
sent_per_second = 0
|
22
|
-
emails.each do |email|
|
23
|
-
if exceed_quota?
|
24
|
-
log "exceed daily quota in #{@quota}, sent by mailer #{@sent_count}, other #{@sent_last_24_hours}"
|
25
|
-
return
|
26
|
-
end
|
27
|
-
begin
|
28
|
-
if sent_per_second == options.rate
|
29
|
-
sleep 1
|
30
|
-
sent_per_second = 0
|
31
|
-
else
|
32
|
-
sent_per_second += 1
|
33
|
-
end
|
34
|
-
log "send email to #{email.to}"
|
35
|
-
@ses.send_raw_email email.mail, from: email.from, to: email.to
|
36
|
-
email.destroy
|
37
|
-
@sent_count += 1
|
38
|
-
rescue => e
|
39
|
-
log "ERROR sending email #{email.id} - #{email.inspect}: #{e.message}\n #{e.backtrace.join("\n ")}", :error
|
40
|
-
ArMailerAWS.error_proc.call(email, e) if ArMailerAWS.error_proc
|
41
|
-
email.update_column(:last_send_attempt_at, Time.now)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def exceed_quota?
|
47
|
-
if @day == Date.today
|
48
|
-
options.quota <= @sent_count + sent_last_24_hours
|
49
|
-
else
|
50
|
-
@sent_count = 0
|
51
|
-
@sent_last_24_hours = nil
|
52
|
-
false
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def sent_last_24_hours
|
57
|
-
@sent_last_24_hours ||= begin
|
58
|
-
count = @ses.quotas[:sent_last_24_hours]
|
59
|
-
log "#{count} emails sent last 24 hours"
|
60
|
-
count
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def find_emails
|
65
|
-
@model.where('last_send_attempt_at IS NULL OR last_send_attempt_at < ?', Time.now - 300).limit(options.batch_size)
|
66
|
-
end
|
67
|
-
|
68
|
-
def cleanup
|
69
|
-
return if options.max_age.to_i.zero?
|
70
|
-
timeout = Time.now - options.max_age
|
71
|
-
emails = @model.destroy_all(['last_send_attempt_at IS NOT NULL AND created_at < ?', timeout])
|
72
|
-
|
73
|
-
log "expired #{emails.length} emails"
|
74
|
-
end
|
75
|
-
|
76
|
-
def log(msg, level=:info)
|
77
|
-
formatted_msg = "[#{Time.now}] ar_mailer_aws: #{msg}"
|
78
|
-
puts formatted_msg if options.verbose
|
79
|
-
if logger
|
80
|
-
logger.send(level, msg)
|
81
|
-
elsif options.verbose && defined? Rails
|
82
|
-
Rails.logger.send(level, formatted_msg)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def logger
|
87
|
-
ArMailerAWS.logger
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|