itrp-export-monitor 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 ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YmVjNzQ1ZDYzMWE0MGI1MjA1ZGI0NDc3NjVlNGFjZTZjYjM3YmQxMw==
5
+ data.tar.gz: !binary |-
6
+ YmU3ZGE5NTE1ZDYzYzUyZjgyN2I0ZjY0OGFjN2QxMGJmOTZjNzU0Zg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MmQ4YmZiMDUyZTg3YTZlZDMzZWY1ODBlZjA5YjFiMWFhNTJjMDM4ZDQ0OGU5
10
+ OWQ1OGUwYzc3NDUxZGQ5ZTZkOTk4YjJhNzBmOGIxZjIwZGU5ZDVlOTRhY2Zl
11
+ YjIzMDNiMzM2MWVjOWViOTkwN2YxY2QxYjE1ZDY1YmI4MWJkYmQ=
12
+ data.tar.gz: !binary |-
13
+ YWM5ZjZlYjFhZDEyZDgzMzc3MmExMTM2MDRiMjJkODc3ZWM0MTJhZmIzMTRj
14
+ ZTRlMzExNTZmNDIxOTk2Y2QxNGNjMjE4OTJlZmNmN2ZkZWM0ZjMyYmFhMWIz
15
+ MjhjZWEwNTFlMzYzYzliYjM2ZjgzMWU4MGFlZGUxNWRlYmFlMGY=
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in itrp-client.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,69 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ itrp-export-monitor (1.0.0)
5
+ active_support
6
+ clacks (>= 1.0.0)
7
+ gem_config
8
+ i18n
9
+ itrp-client
10
+ rubyzip
11
+
12
+ GEM
13
+ remote: https://rubygems.org/
14
+ specs:
15
+ active_support (3.0.0)
16
+ activesupport (= 3.0.0)
17
+ activesupport (3.0.0)
18
+ addressable (2.3.5)
19
+ clacks (1.0.4)
20
+ mail
21
+ crack (0.4.1)
22
+ safe_yaml (~> 0.9.0)
23
+ diff-lcs (1.2.5)
24
+ docile (1.1.0)
25
+ gem_config (0.2.4)
26
+ i18n (0.6.5)
27
+ itrp-client (1.0.1)
28
+ activesupport
29
+ gem_config
30
+ mime-types
31
+ mail (2.5.4)
32
+ mime-types (~> 1.16)
33
+ treetop (~> 1.4.8)
34
+ mime-types (1.25)
35
+ multi_json (1.8.2)
36
+ polyglot (0.3.3)
37
+ rake (10.1.0)
38
+ rspec (2.14.1)
39
+ rspec-core (~> 2.14.0)
40
+ rspec-expectations (~> 2.14.0)
41
+ rspec-mocks (~> 2.14.0)
42
+ rspec-core (2.14.7)
43
+ rspec-expectations (2.14.4)
44
+ diff-lcs (>= 1.1.3, < 2.0)
45
+ rspec-mocks (2.14.4)
46
+ rubyzip (1.1.0)
47
+ safe_yaml (0.9.7)
48
+ simplecov (0.8.2)
49
+ docile (~> 1.1.0)
50
+ multi_json
51
+ simplecov-html (~> 0.8.0)
52
+ simplecov-html (0.8.0)
53
+ treetop (1.4.15)
54
+ polyglot
55
+ polyglot (>= 0.3.1)
56
+ webmock (1.16.0)
57
+ addressable (>= 2.2.7)
58
+ crack (>= 0.3.2)
59
+
60
+ PLATFORMS
61
+ ruby
62
+
63
+ DEPENDENCIES
64
+ bundler (~> 1.3)
65
+ itrp-export-monitor!
66
+ rake
67
+ rspec
68
+ simplecov
69
+ webmock
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 ITRP Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,322 @@
1
+ # ITRP Export Monitor
2
+
3
+
4
+ The itrp-export-monitor gem makes it easy to monitor a mailbox
5
+ receiving [Scheduled Exports](http://help.itrp.com/help/export_fields) from ITRP
6
+ and to store the incoming export files on disk or forward them to an FTP server.
7
+
8
+ This readme will take you through all the steps of setting up an Export Monitor:
9
+
10
+ * [Install Ruby, bundler and create a Gemfile](#installation)
11
+ * [Generate an Export Monitor](#generate-an-export-monitor)
12
+ * [Customize the configuration](#configuration)
13
+ * [Start the Export Monitor](#start-the-export-monitor)
14
+ * [Monitor the Export Monitor](#monitor-the-export-monitor)
15
+
16
+
17
+ Installation
18
+ ------------
19
+
20
+ 1. [Install Ruby 1.9.3 or higher](https://www.ruby-lang.org/en/downloads/)
21
+ 2. Create a *root directory* for the ITRP Export Monitor and go to that directory, e.g.
22
+ ```
23
+ $ mkdir /usr/local/itrp_exports
24
+ $ cd /usr/local/itrp_exports
25
+ ```
26
+ 3. Install [Bundler](http://bundler.io/)
27
+ ```
28
+ $ gem install bundler
29
+ ```
30
+ 4. Create a [`Gemfile`](http://bundler.io/v1.3/gemfile.html) in the *root directory* with the following contents:
31
+ ```
32
+ source 'https://rubygems.org'
33
+
34
+ gem 'itrp-export-monitor'
35
+
36
+ ```
37
+ 5. Finally download the gems using bundler:
38
+ ```
39
+ $ bundle
40
+ ```
41
+
42
+ Generate an Export Monitor
43
+ --------------------------
44
+
45
+ An Export Monitor is basically a ruby file that [configures](#configuration) the monitor
46
+ and then fires it up to start looking for finished exports in the mailbox.
47
+
48
+ To help you create the ruby file, the following generator is available from the *root directory*:
49
+ ```
50
+ $ itrp-export-monitor generate[<export ID>,<email address>,<imap password>]
51
+ ```
52
+
53
+ The **export ID** is the unique identifier of the Scheduled Export in ITRP
54
+ and can be found in the address bar of the browser when you view
55
+ the Scheduled Export in ITRP at `https://<your domain>.itrp.com/exports`.
56
+
57
+ The **email address** is the email address where the export file is sent to.
58
+ This is the email address of the user defined in the *Run as* field.
59
+ **We strongly recommend to [create a separate mailbox](setup-a-mailbox) for the export monitor.**
60
+
61
+ The **password** is the password with which the IMAP server can be accessed for this email address.
62
+
63
+ The default configuration is set up to work with [GMail](http://mail.google.com) (in English)
64
+ and copies completed export files to `/tmp/exports`.
65
+
66
+ Below is an example of the generated configuration:
67
+
68
+ ```
69
+ $ cd /usr/local/itrp_exports
70
+ $ itrp-export-monitor generate[777,test.my.export@gmail.com,easy_to_gu3ss]
71
+ ```
72
+
73
+ **/usr/local/itrp_exports/export_monitor.777.rb**
74
+
75
+ ```
76
+ require 'itrp/export/monitor'
77
+
78
+ # the location where all the run-time information on the Export Monitor is stored
79
+ BASE_DIR = "/usr/local/itrp_exports/export_monitor_777"
80
+ FileUtils.mkpath "#{BASE_DIR}/log"
81
+
82
+ Itrp::Export::Monitor.configure do |export|
83
+ export.root = BASE_DIR
84
+ export.logger = Logger.new("#{BASE_DIR}/log/export_monitor.777.log")
85
+ export.ids = [777]
86
+ export.unzip = true
87
+ export.sub_dirs = false
88
+
89
+ export.to = '/tmp/exports'
90
+ # export.to_ftp = 'ftp.mycompany.com'
91
+ # export.to_ftp_dir = 'my/exports'
92
+ # export.ftp_user_name = 'user'
93
+ # export.ftp_password = 'secret'
94
+
95
+ export.imap_address = 'imap.googlemail.com'
96
+ export.imap_port = 993
97
+ export.imap_user_name = 'test.my.export@gmail.com'
98
+ export.imap_password = 'easy_to_gu3ss'
99
+ export.imap_ssl = true
100
+ export.imap_mailbox = 'INBOX'
101
+ export.imap_archive = '[Gmail]/All Mail'
102
+ end
103
+
104
+ Itrp::Export::Monitor.run
105
+ ```
106
+
107
+
108
+ Configuration
109
+ -------------
110
+
111
+ Before you [start the Export Monitor](#start-the-export-monitor)
112
+ you need to customize the [generated configuration](#generate-an-export-monitor).
113
+
114
+ The Export Monitor configuration is defined using a block:
115
+ ```
116
+ Itrp::Export::Monitor.configure do |export|
117
+ export.root = '/usr/local/...'
118
+ export.ids = [777, 779]
119
+ ...
120
+ end
121
+ ```
122
+
123
+ All options available:
124
+
125
+ * _logger_: The Ruby Logger instance, default: `Logger.new(STDOUT)`
126
+ * _daemonize_: Set to `true` to run in daemon mode; not available on Windows (default: `false`)
127
+ * _exit_when_idle_: Number of minutes after which the service stops when no Export mails are found (default: `-1` no exit)
128
+ * _root_: **required** The root directory to store Export Monitor logs, pids and downloads
129
+ * _id/ids_: **required** The id(s) of the Scheduled Exports to monitor, e.g. `[777]`
130
+ * _unzip_: Unzip the CSV files, default: `true`.
131
+ * _sub_dirs_: Place each CSV file in a different subdirectory based on the export type (default: `false`)
132
+ * _to_: Directory to store export files on local disk, e.g. `'/tmp/people_export'`
133
+ * _to_ftp_: The address of the FTP server to sent the completed downloads to, e.g. `'ftp.mycompany.com'`
134
+ * _to_ftp_dir_: The subdirectory on the FTP server, e.g. `'my/downloads'` (default: `'.'`)
135
+ * _ftp_user_name_: The user name to access the FTP server
136
+ * _ftp_password_: The password to access the FTP server
137
+ * _imap_address_: **required** The address of the IMAP mail server (default: `'imap.googlemail.com'`)
138
+ * _imap_port_: The port of the IMAP mail server (default: `993`)
139
+ * _imap_ssl_: Set to +false+ to disabled SSL (default: `true`)
140
+ * _imap_user_name_: **required** The user name to access the IMAP server
141
+ * _imap_password_: **required** The password to access the IMAP server
142
+ * _imap_mailbox_: The mailbox to monitor for ITRP export mails (default: `'INBOX'`)
143
+ * _imap_archive_: The archive mailbox to store the processed ITRP export mails (default: `'[Gmail]/All Mail'`)
144
+ * _on_exception_: A Proc that takes an exception and the mail as an argument: `Proc.new{ |ex, mail| ... }`.
145
+ All exceptions will also be logged as errors in the logfile.
146
+ * _csv_row_sep_: Set the CSV row separator (default: `:auto`, i.e. windows/unix newline)
147
+ * _csv_col_sep_: Set the CSV column separator (default: `','`)
148
+ * _csv_quote_char_: Set the CSV quote character, at most 1 character (default: `'"'`)
149
+ * _csv_value_proc_: Provide a procedure to change values before adding them to the CSV, e.g.
150
+ `Proc.new{ |value| value.gsub(/\r?\n/, ' ')`
151
+
152
+
153
+ Start the Export Monitor
154
+ ------------------------
155
+
156
+ To start an Export Monitor simply run the following command:
157
+
158
+ ```
159
+ $ cd /usr/local/itrp_exports
160
+ $ bundle exec ruby export_monitor.777.rb
161
+ ```
162
+
163
+ If the configuration is correct, the Export Monitor will startup and keep on running until it
164
+ receives a *QUIT* signal (by pressing `<ctrl>-C`).
165
+
166
+ If the Export Monitor stops running immediately the configuration is probably incorrect.
167
+ The log file will contain the details on what went wrong, see:
168
+ ```
169
+ $ less /usr/local/itrp_exports/export_monitor_777/log/export_monitor.777.log
170
+ ```
171
+
172
+ If the Export Monitor is running, but the exports are not picked up and processed as expected, also check
173
+ the log file first.
174
+
175
+ On startup the following directory structure will be created in the *export.root* directory:
176
+
177
+ ```
178
+ /usr
179
+ /local
180
+ /itrp_exports
181
+ /export_monitor_777
182
+ /downloads
183
+ ...
184
+ /log
185
+ export_monitor.777.log
186
+ /pids
187
+ export_monitor.777.pid
188
+ /tmp
189
+ clacks_config.export_monitor.777.rb
190
+ ```
191
+
192
+ The `downloads` subdirectory contains the downloaded csv/zip files. These files are not
193
+ deleted automatically, so it is advisable to [monitor the disk usage](#disk-usage).
194
+
195
+ The `log` directory contains the log file of the Export Monitor.
196
+
197
+ The `pids` directory contains the pid file of the Export Monitor.
198
+
199
+ The `tmp` directory contains temporary files to run the Export Monitor and should be left alone.
200
+
201
+
202
+ Monitoring the Export Monitor
203
+ -----------------------------
204
+
205
+ The process ID of the Export Monitor will be stored in the `<root dir>/pids/export_monitor.<id>.pid` file.
206
+ Tools like [Monit](http://mmonit.com/monit/) can be used to make sure the Export Monitor is always running.
207
+
208
+ When an incoming export could not be processed correctly, an error is logged in the logfile
209
+ which is located in `<root dir>/log/export_monitor.<id>.log`. You should either watch this logfile for
210
+ errors, or define a custom *on_exception* handler to take the [appropriate action](#recovering-from-errors).
211
+
212
+ Below is an example of an *on_exception* handler sending a mail to a systems mailbox using
213
+ the [mail gem](https://github.com/mikel/mail).
214
+
215
+ ```
216
+ Itrp::Export::Monitor.configure do |export|
217
+ ...
218
+ export.on_exception = Proc.new do |ex, mail|
219
+ Mail.deliver do
220
+ from 'export.monitor@mycompany.example.com'
221
+ to 'sysadmin@mycompany.example.com'
222
+ subject "Unable to process incoming export mail: #{mail.original.subject}"
223
+ body ex.message
224
+ end
225
+ end
226
+ end
227
+ ```
228
+
229
+ #### Recovering from errors
230
+
231
+ When an export mail could not be processed correctly:
232
+
233
+ 1. the error is logged in the logfile (once)
234
+ 2. the *on_exception* handler is called (once)
235
+ 3. the mail is left in the *imap_mailbox*
236
+
237
+ After that the mail will not be processed again unless the Export Monitor is restarted.
238
+
239
+ #### Disk usage
240
+
241
+ All export files that are downloaded are kept in the `<export.root>/downloads` directory.
242
+ These files are not deleted automatically, so you might want to add a job to cleanup this
243
+ directory every month/year depending on your setup.
244
+
245
+ #### Not a real service?
246
+
247
+ The `exit_when_idle` option may be used to stop the Export Monitor when there are no new
248
+ export mails coming in for a couple of minutes. This may be useful in case you want to
249
+ fire up the Export Monitor at a scheduled time using the cron-tab or Windows Task Scheduler.
250
+
251
+ Other considerations
252
+ --------------------
253
+
254
+ #### Setup a mailbox
255
+
256
+ It is best to create a separate user in ITRP with a corresponding mailbox for each Export Monitor.
257
+
258
+ For example: if the Export Monitor will monitor the Weekly Full People, you could create a
259
+ [GMail](http://mail.google.com) for *people.monitor.mycompany@gmail.com*. Then create a new
260
+ [ITRP User](http://developer.itrp.com/v1/general/getting_started/) using that email address.
261
+
262
+ That's it. Now you are ready to [generate an Export Monitor](#generate-an-export-monitor).
263
+
264
+ #### Not a dedicated mailbox?
265
+
266
+ The Export Monitor will search all mails in the *imap_mailbox* for export mails sent by ITRP.
267
+ When an export mail is found and the export ID matches one of the *ids* in the
268
+ [configuration](#configuration), the mail is processed. Other mails in the same *imap_mailbox*
269
+ are left alone.
270
+
271
+ If there are a lot of mails kept in the mailbox the processing may slow down.
272
+ It is advisable to create a separate user in ITRP with the Account Administrator role and
273
+ it's own mailbox for processing exports.
274
+
275
+ That user should then be selected as the *Run as* user in the Scheduled Export.
276
+
277
+ #### IMAP
278
+
279
+ All options starting with the *imap* prefix are used to access the mailbox to monitor incoming mails.
280
+ By default the configuration is setup for [GMail](http://mail.google.com).
281
+
282
+ Contact the System Administrator of your mailbox in case you are not sure how to setup the IMAP
283
+ configuration.
284
+
285
+ #### Multiple Scheduled Exports
286
+
287
+ To monitor and process multiple Scheduled Exports simply provide all the export IDs to monitor
288
+ in the [configuration](#configuration):
289
+
290
+ ```
291
+ export.ids = [777, 785, 786]
292
+ ```
293
+
294
+ #### Forwarding export mails
295
+
296
+ Forwarded mails will not be processed as the Export Monitor depends on the ITRP
297
+ [mail message headers](http://developer.itrp.com/v1/export/#downloading-an-export-file) to be available in the mail.
298
+ When a mail is forwarded these headers may not be found and the export mail is not processed.
299
+
300
+ #### Export files that are partially copied
301
+
302
+ To prevent issues with partial files the export monitor will append `.in_progress` to export files
303
+ that are being copied/FTP'd. Once the copy is completed, the file is renamed and the `.in_progress` suffix
304
+ is removed.
305
+
306
+ Another way to prevent issues with partially copied files is to try to obtain a write lock on the file
307
+ before processing the export file. The OS will return an error when the file is not completely copied.
308
+
309
+ #### CSV not fully supported?
310
+
311
+ Some systems, like [SAP BI](http://scn.sap.com/thread/3200938), cannot handle well-defined CSV files.
312
+ As a workaround the Export Monitor can rewrite the CSV files in a different format. To do so, take a look
313
+ at the [configuration](#configuration).
314
+
315
+ The most common issue is that new-lines within a value are not handled correctly when the new-line is also used
316
+ as a row separator. One way to deal with that is to replace the new-lines in the values with a different value:
317
+
318
+ ```
319
+ Itrp::Export::Monitor.configure do |export|
320
+ export.csv_value_proc = Proc.new{ |value| value.gsub(/\r?\n/, ' ') }
321
+ end
322
+ ```
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rake'
3
+ require 'bundler'
4
+
5
+ raise "Bundler is required. Please install bundler with 'gem install bundler'" unless defined?(Bundler)
6
+
7
+ # Example:
8
+ # itrp-export-monitor generate[<export_id>]
9
+
10
+ # init dependencies
11
+ Bundler.setup
12
+
13
+ # init rake
14
+ Rake.application.init
15
+
16
+ # load the rake tasks
17
+ gem_dir = File.expand_path('..',File.dirname(__FILE__))
18
+ load "#{gem_dir}/lib/itrp/export/monitor/tasks/itrp_export_monitor.rake"
19
+
20
+ # invoke the given task
21
+ Rake.application.invoke_task("itrp_export_monitor:#{ARGV[0]}")
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'itrp/export/monitor/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'itrp-export-monitor'
8
+ spec.version = Itrp::Export::Monitor::VERSION
9
+ spec.platform = Gem::Platform::RUBY
10
+ spec.required_ruby_version = '>= 1.9.3'
11
+ spec.authors = ['ITRP']
12
+ spec.email = %q{developers@itrp.com}
13
+ spec.description = %q{Monitor a mailbox and store Scheduled ITRP Exports to disk or FTP.}
14
+ spec.summary = %q{The itrp-export-monitor gem makes it easy to monitor a mailbox receiving Scheduled Exports from ITRP and to store the incoming export files on disk or forward it to an FTP server.}
15
+ spec.homepage = %q{https://github.com/itrp/itrp-export-monitor}
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = Dir.glob("lib/**/*") + %w(LICENSE.txt README.md Gemfile Gemfile.lock itrp-export-monitor.gemspec)
19
+ spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ spec.test_files = `git ls-files -- {test,spec}/*`.split("\n")
21
+ spec.require_paths = ['lib']
22
+ spec.rdoc_options = ['--charset=UTF-8']
23
+
24
+ spec.add_runtime_dependency 'gem_config'
25
+ spec.add_runtime_dependency 'itrp-client'
26
+ spec.add_runtime_dependency 'active_support'
27
+ spec.add_runtime_dependency 'rubyzip'
28
+ spec.add_runtime_dependency 'clacks', '>= 1.0.0'
29
+ spec.add_dependency 'i18n'
30
+
31
+ spec.add_development_dependency 'bundler', '~> 1.3'
32
+ spec.add_development_dependency 'rake'
33
+ spec.add_development_dependency 'rspec'
34
+ spec.add_development_dependency 'webmock'
35
+ spec.add_development_dependency 'simplecov'
36
+ end
@@ -0,0 +1,118 @@
1
+ require 'gem_config'
2
+ require 'logger'
3
+ require 'mail'
4
+
5
+ require 'itrp/client' # this will load CA Certificate bundle
6
+ require 'clacks'
7
+
8
+ require 'itrp/export/monitor/service'
9
+ require 'itrp/export/monitor/version'
10
+ require 'itrp/export/monitor/mail'
11
+ require 'itrp/export/monitor/exchange'
12
+
13
+ require 'active_support/core_ext/module/aliasing.rb'
14
+ require 'active_support/core_ext/object/blank'
15
+ require 'active_support/core_ext/object/try.rb'
16
+ require 'active_support/core_ext/hash/indifferent_access'
17
+
18
+ module Itrp
19
+ module Export
20
+
21
+ # Configuration for ITRP Export Monitor:
22
+ # Itrp::Export::Monitor.configure do |export|
23
+ # export.name = 'people_export'
24
+ # export.root = File.expand_path('../exports', __FILE__)
25
+ # export.id = 8713
26
+ # export.to = "#{export.root}/people"
27
+ # export.imap_user_name = 'test.exports@gmail.com'
28
+ # export.imap_password = 'secret'
29
+ # ...
30
+ # end
31
+ #
32
+ # Start the ITRP Export Monitor:
33
+ # Itrp::Export::Monitor.run
34
+ #
35
+ # All options available:
36
+ # - logger: The Ruby Logger instance, default: Logger.new(STDOUT)
37
+ # - daemonize: Set to +true+ to run in daemon mode; not available on Windows (default: false)
38
+ # - name: *required* The name of the export
39
+ # - root: *required* The root directory to store export monitor logs, pids and downloads
40
+ # - id/ids: *required* The id(s) of the scheduled exports to monitor
41
+ # - to: Location to store export files (default = <root>/ready)
42
+ # - to_ftp: The address of the FTP server to sent the completed downloads to
43
+ # - to_ftp_dir: The subdirectory on the FTP server (default = '.')
44
+ # - ftp_user_name: The user name to access the FTP server
45
+ # - ftp_password: The password to access the FTP server
46
+ # - imap_address: The address of the IMAP mail server (default: 'imap.googlemail.com')
47
+ # - imap_port: The port of the IMAP mail server (default: 993)
48
+ # - imap_ssl: Set to +false+ to disabled SSL (default: true)
49
+ # - imap_user_name: *required* The user name to access the IMAP server
50
+ # - imap_password: *required* The password to access the IMAP server
51
+ # - imap_mailbox: The mailbox to monitor for ITRP export mails (default: 'INBOX')
52
+ # - imap_archive: The archive mailbox to store the processed ITRP export mails (default: '[Gmail]/All Mail')
53
+ # - on_exception: A Proc that takes an exception and the mail as an argument: Proc.new{ |ex, mail| ... }
54
+ module Monitor
55
+ include GemConfig::Base
56
+
57
+ with_configuration do
58
+ has :logger, classes: ::Logger, default: ::Logger.new(STDOUT)
59
+
60
+ has :daemonize, classes: [TrueClass, FalseClass], default: false
61
+ has :exit_when_idle, classes: Fixnum, default: -1
62
+ has :root, classes: String
63
+ has :id, classes: Fixnum
64
+ has :ids, classes: Array
65
+ has :unzip, classes: [TrueClass, FalseClass], default: true
66
+ has :sub_dirs, classes: [TrueClass, FalseClass], default: false
67
+
68
+ has :to, classes: String
69
+
70
+ has :to_ftp, classes: String
71
+ has :to_ftp_dir, classes: String, default: '.'
72
+ has :ftp_user_name, classes: String
73
+ has :ftp_password, classes: String
74
+
75
+ has :imap_address, classes: String, default: 'imap.googlemail.com'
76
+ has :imap_port, classes: Fixnum, default: 993
77
+ has :imap_ssl, classes: [TrueClass, FalseClass], default: true
78
+ has :imap_user_name, classes: String
79
+ has :imap_password, classes: String
80
+
81
+ has :imap_mailbox, classes: String, default: 'INBOX'
82
+ has :imap_archive, classes: String, default: '[Gmail]/All Mail'
83
+
84
+ has :on_exception, classes: Proc
85
+
86
+ has :csv_row_sep, classes: String
87
+ has :csv_col_sep, classes: String
88
+ has :csv_quote_char, classes: String
89
+ has :csv_value_proc, classes: Proc
90
+ end
91
+
92
+ class << self
93
+
94
+ def run
95
+ # create the export monitor as a singleton
96
+ @service = Itrp::Export::Monitor::Service.new
97
+ # generate clacks file
98
+ clacks_config_filename = @service.generate_clacks_config
99
+ # start clacks with the generated config
100
+ args = ['-c', clacks_config_filename]
101
+ args << '-D' if @service.option(:daemonize)
102
+ Clacks::Command.new(args).exec
103
+ # return the singleton instance
104
+ @service
105
+ end
106
+
107
+ def process(mail)
108
+ @service.process(mail)
109
+ end
110
+
111
+ def logger
112
+ configuration.logger
113
+ end
114
+ end
115
+
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,139 @@
1
+ require 'fileutils'
2
+ require 'net/ftp'
3
+ require 'zip'
4
+ require 'csv'
5
+
6
+ module Itrp
7
+ module Export
8
+ module Monitor
9
+
10
+ class Exchange
11
+ attr_accessor :options, :fullpath, :basename
12
+
13
+ def initialize(export_filename, options)
14
+ @options = options
15
+ @options[:to_ftp_dir].gsub!('\\', '/')
16
+ @fullpath = export_filename
17
+ @basename = File.basename(export_filename)
18
+ @logger = @options[:logger]
19
+ end
20
+
21
+ def transfer
22
+ files = transfer_files
23
+ local_transfer(files) unless option(:to).blank?
24
+ ftp_transfer(files) unless option(:to_ftp).blank?
25
+ end
26
+
27
+ private
28
+
29
+ def option(key)
30
+ @options[key]
31
+ end
32
+
33
+ # return an hash with files that need to be transferred {<local file name>: <remote file path>}
34
+ def transfer_files
35
+ # unzip if needed
36
+ files = option(:unzip) && @fullpath =~ /\.zip$/ ? unzip : {@fullpath => @basename}
37
+ if option(:unzip)
38
+ # prepend the export type as a subdirectory in the target path
39
+ files.each_value{ |target| target.insert(0, "#{target[/.*-(.*)\.csv$/, 1]}/")} if option(:sub_dirs)
40
+ # convert the CSV files
41
+ files.each_key{ |source| convert_csv(source) } unless [:csv_row_sep, :csv_col_sep, :csv_quote_char, :csv_value_proc].all?{ |csv_option| option(csv_option).blank? }
42
+ end
43
+ files
44
+ end
45
+
46
+ # unzip all files to tmp directory
47
+ def unzip
48
+ files = {}
49
+ unzip_dir = @fullpath[0..-5]
50
+ FileUtils.mkpath(unzip_dir)
51
+ Zip::File.open(@fullpath) do |zipfile|
52
+ zipfile.each do |entry|
53
+ next unless entry.file?
54
+ full_source_path = "#{unzip_dir}/#{entry.name}"
55
+ entry.extract(full_source_path) { true } # true for overwrite
56
+ files[full_source_path] = entry.name
57
+ end
58
+ end
59
+ files
60
+ end
61
+
62
+ # Convert the CSV files using the CSV options
63
+ def convert_csv(original_csv)
64
+ csv_options = {}
65
+ csv_options[:col_sep] = option(:csv_col_sep) unless option(:csv_col_sep).blank?
66
+ csv_options[:row_sep] = option(:csv_row_sep) unless option(:csv_row_sep).blank?
67
+ csv_options[:quote_char] = option(:csv_quote_char) unless option(:csv_quote_char).blank?
68
+ value_converter = option(:csv_value_proc)
69
+
70
+ converted_csv = "#{original_csv}.converting"
71
+ CSV.open(converted_csv, 'wb', csv_options) do |csv|
72
+ CSV.foreach(original_csv) do |row|
73
+ row = row.map{ |value| value.blank? ? '' : value_converter.call(value) } if value_converter
74
+ csv << row
75
+ end
76
+ end
77
+ FileUtils.remove(original_csv)
78
+ FileUtils.move(converted_csv, original_csv)
79
+ end
80
+
81
+ def local_transfer(files)
82
+ files.each do |full_source_path, relative_target_path|
83
+ full_target_path = "#{option(:to)}/#{relative_target_path}"
84
+ local_copy(full_source_path, full_target_path)
85
+ end
86
+ @logger.info { "Copied #{files.size} file(s) from '#{@fullpath}' to '#{option(:to)}'" }
87
+ end
88
+
89
+ # copy a local file and make sure the directories are created
90
+ def local_copy(source, target)
91
+ FileUtils.mkpath(File.dirname(target))
92
+ FileUtils.copy(source, "#{target}.in_progress")
93
+ FileUtils.move("#{target}.in_progress", target)
94
+ end
95
+
96
+ def ftp_transfer(files)
97
+ Net::FTP.open(option(:to_ftp), option(:ftp_user_name), option(:ftp_password)) do |ftp|
98
+ files.each do |full_source_path, relative_target_path|
99
+ ftp_copy(ftp, full_source_path, "#{option(:to_ftp_dir)}/#{relative_target_path}")
100
+ end
101
+ end
102
+ @logger.info { "Copied #{files.size} file(s) from '#{@fullpath}' to '#{option(:to_ftp)}/#{option(:to_ftp_dir)}'" }
103
+ end
104
+
105
+ # copy a file from the local disk to a remote FTP server
106
+ # it is possible to use a path in the remote file, e.g. 'dir1/dir2/remote_file.txt'
107
+ def ftp_copy(ftp, local_file, remote_file)
108
+ in_ftp_dir(ftp, File.dirname(remote_file)) do |_ftp|
109
+ basename = File.basename(remote_file)
110
+ _ftp.putbinaryfile(local_file, "#{basename}.in_progress")
111
+ _ftp.rename("#{basename}.in_progress", basename)
112
+ end
113
+ end
114
+
115
+ # move to the given subdirectory (create directories on the fly) and yield
116
+ # the ftp directory will be reset afterwards
117
+ def in_ftp_dir(ftp, path, &block)
118
+ if path.blank? || path == '.'
119
+ yield ftp
120
+ else
121
+ pwd = ftp.pwd
122
+ begin
123
+ # move to the directory, ignore / at start or end, and paths with only dots like . and ..
124
+ path.split('/').reject{|dir| dir.blank? || dir =~ /\.+/}.each do |dir|
125
+ begin ftp.mkdir(dir); rescue ::Exception => e; end
126
+ ftp.chdir(dir)
127
+ end
128
+ yield ftp
129
+ ensure
130
+ ftp.chdir(pwd)
131
+ end
132
+ end
133
+ end
134
+
135
+ end
136
+ end
137
+ end
138
+ end
139
+
@@ -0,0 +1,43 @@
1
+ module Itrp
2
+ module Export
3
+ module Monitor
4
+ class Mail
5
+ def initialize(mail)
6
+ @mail = mail
7
+ end
8
+
9
+ def original
10
+ @mail
11
+ end
12
+
13
+ # value of mail header: X-ITRP-ExportID, e.g. 2
14
+ def export_id
15
+ @export_id ||= @mail.header['X-ITRP-ExportID'].try(:value).try(:to_i)
16
+ end
17
+
18
+ # value of mail header: X-ITRP-Export, e.g. '0fad4fc0fd4a0130ad2a12313b0e50759969ab71899d2bb1d3e3d8f66e6e5133'
19
+ def token
20
+ @token ||= @mail.header['X-ITRP-Export'].try(:value)
21
+ end
22
+
23
+ # First hyperlink in the text, e.g. https://itrp.amazonaws.com/exports/20130911/wdc/20130911-195545-affected_slas.csv?AWSAccessKeyId=AKIA&Signature=du%2B23ZUsrLng%3D&Expires=1379102146
24
+ def download_uri
25
+ return nil unless self.export_id
26
+ # the first match from https:// until a space or the end of the line
27
+ @download_uri ||= @mail.text_part.body.decoded[/(https?:\/\/[^\s$]+)/, 1]
28
+ end
29
+
30
+ # the filename of the csv or zip file
31
+ def filename
32
+ return nil if self.download_uri.blank?
33
+ @filename ||= self.download_uri[/\/([^\/]+\.(?:csv|zip))\?/, 1]
34
+ end
35
+
36
+ # ignore the message
37
+ def ignore
38
+ @mail.skip_deletion
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,153 @@
1
+ require 'fileutils'
2
+ require 'open-uri'
3
+
4
+ module Itrp
5
+ module Export
6
+ module Monitor
7
+
8
+ class Service
9
+ def initialize
10
+ @failed_exports = Set.new
11
+ @missing_export_ids = Set.new
12
+ @options = Itrp::Export::Monitor.configuration.current
13
+ @options[:ids] = (@options[:ids] || []) + [@options[:id]].flatten.compact.map(&:to_i)
14
+ [:root, :ids, :imap_user_name, :imap_password].each do |required_option|
15
+ raise ::Itrp::Exception.new("Missing required configuration option #{required_option}") if option(required_option).blank?
16
+ end
17
+ [:sub_dirs, :csv_row_sep, :csv_col_sep, :csv_quote_char, :csv_value_proc].each do |unzip_dependent_option|
18
+ raise ::Itrp::Exception.new("Configuration option #{unzip_dependent_option} is only available when unzip is true") unless option(unzip_dependent_option).blank?
19
+ end unless @options[:unzip]
20
+ raise ::Itrp::Exception.new("Configuration option csv_quote_char must be 1 character long") unless option(:csv_quote_char).blank? || option(:csv_quote_char).length == 1
21
+ @logger = @options[:logger]
22
+ create_exit_when_idle_timer
23
+ end
24
+
25
+ # Retrieve an option
26
+ def option(key)
27
+ @options[key]
28
+ end
29
+
30
+ def process(mail)
31
+ mail = Itrp::Export::Monitor::Mail.new(mail)
32
+ return if @failed_exports.include?(mail.download_uri)
33
+
34
+ if option(:ids).include?(mail.export_id)
35
+ begin
36
+ kill_exit_when_idle_timer
37
+ @logger.info { "Processing ITRP Export mail:\n Subject: #{mail.original.subject}\n Export ID: #{mail.export_id}\n Token: #{mail.token}\n URI: #{mail.download_uri}" }
38
+ store_export(mail)
39
+ rescue ::Exception => ex
40
+ @failed_exports.add(mail.download_uri)
41
+ @logger.error { "Processing of mail '#{mail.original.subject}' failed: #{ex.message}\n #{ex.backtrace.join("\n ")}" }
42
+ handle_exception(ex, mail)
43
+ mail.ignore # leave mail in the mailbox
44
+ ensure
45
+ create_exit_when_idle_timer
46
+ end
47
+ else
48
+ mail.ignore # leave mail in the mailbox
49
+ unless @missing_export_ids.include?(mail.export_id)
50
+ @missing_export_ids.add(mail.export_id)
51
+ @logger.info { mail.export_id ? "Skipping mail. ITRP Export ID #{mail.export_id} not configured for monitoring" : "Skipping mail. Not an ITRP Export mail: #{mail.original.subject}" }
52
+ end
53
+ end
54
+ end
55
+
56
+ # Generate a clacks config file based on the export config
57
+ def generate_clacks_config
58
+ clacks_config_filename = "#{dir(:tmp)}/clacks_config.#{monitor_id}.rb"
59
+ log_device = @logger.instance_variable_get(:@logdev)
60
+ log_file = log_device.respond_to?(:filename) && log_device.filename ? File.expand_path(log_device.filename) : "#{dir(:log)}/#{monitor_id}.log"
61
+ File.open(clacks_config_filename, 'w') do |clacks_config|
62
+ clacks_config.write(<<EOF)
63
+ # -- DO NOT EDIT --
64
+ # Generated by the Export Monitor
65
+
66
+ pid "#{dir(:pids)}/#{monitor_id}.pid"
67
+ stdout_path "#{log_file}"
68
+ stderr_path "#{log_file}"
69
+
70
+ imap({
71
+ address: '#{option(:imap_address)}',
72
+ port: #{option(:imap_port)},
73
+ user_name: '#{option(:imap_user_name)}',
74
+ password: '#{option(:imap_password)}',
75
+ enable_ssl: #{option(:imap_ssl)}
76
+ })
77
+
78
+ find_options({
79
+ mailbox: '#{option(:imap_mailbox)}',
80
+ archivebox: '#{option(:imap_archive)}',
81
+ keys: 'FROM ITRP HEADER X-ITRP-ExportID ""',
82
+ delete_after_find: true # Note that only the processed export mails will be deleted
83
+ })
84
+
85
+ on_mail do |mail|
86
+ Itrp::Export::Monitor.process(mail)
87
+ end
88
+ EOF
89
+ end
90
+ clacks_config_filename
91
+ end
92
+
93
+ private
94
+
95
+ def store_export(mail)
96
+ # download export file to the downloads directory
97
+ export_file_name = download_export(mail)
98
+ # and tranfer the files
99
+ Itrp::Export::Monitor::Exchange.new(export_file_name, @options).transfer
100
+ end
101
+
102
+ def download_export(mail)
103
+ local_filename = "#{dir(:downloads)}/#{mail.filename}"
104
+ File.open(local_filename, 'wb') { |f| f.write(open(mail.download_uri).read) }
105
+ local_filename
106
+ end
107
+
108
+ def dir(subdir)
109
+ directory = File.expand_path(subdir.to_s, option(:root))
110
+ FileUtils.mkpath(directory)
111
+ directory
112
+ end
113
+
114
+ def monitor_id
115
+ @monitor_id ||= "export_monitor.#{option(:ids).map(&:to_s).join('.')}"
116
+ end
117
+
118
+ def handle_exception(ex, mail)
119
+ proc = option(:on_exception)
120
+ if proc
121
+ begin
122
+ proc.call(ex, mail)
123
+ rescue ::Exception => another_exception
124
+ @logger.error { "Exception occurred in exception handling: #{another_exception.message}\n #{another_exception.backtrace.join("\n ")}" }
125
+ end
126
+ end
127
+ end
128
+
129
+ def kill_exit_when_idle_timer
130
+ @exit_when_idle_timer.try(:kill)
131
+ @exit_when_idle_timer = nil
132
+ end
133
+
134
+ # reset timer to exit when the export monitor has been idle for a given nr of minutes
135
+ def create_exit_when_idle_timer
136
+ timeout_in_seconds = option(:exit_when_idle).to_i * 60
137
+ return unless timeout_in_seconds > 0
138
+ begin
139
+ kill_exit_when_idle_timer # make sure only 1 timer thread is running at a time
140
+ @exit_when_idle_timer = Thread.new do
141
+ sleep timeout_in_seconds
142
+ stop_signal = (Signal.list.keys & ['QUIT', 'INT']).first
143
+ Process.kill(stop_signal, Process.pid)
144
+ end
145
+ rescue ::Exception => ex
146
+ @logger.error { "Unable to schedule timer to exit when idle in #{timeout_in_seconds} seconds: #{ex.message}\n #{ex.backtrace.join("\n ")}" }
147
+ end
148
+ end
149
+
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,67 @@
1
+ namespace :itrp_export_monitor do
2
+
3
+ GENERATE_HELP = %(Generate a ruby file to configure and start the ITRP Export Monitor:\n itrp-export-monitor generate[<export id>,<email address>,<IMAP password>])
4
+ desc GENERATE_HELP
5
+ task :generate, [:export_id, :email_address, :imap_password] do |t, args|
6
+ check_required_args(args, [:export_id, :email_address, :imap_password], GENERATE_HELP)
7
+ monitor_name = "export_monitor.#{args.export_id}"
8
+ File.open("#{Dir.pwd}/#{monitor_name}.rb", 'w'){ |f| f.write(<<EOF) }
9
+ require 'itrp/export/monitor'
10
+
11
+ # the location where all the run-time information on the export monitor is stored
12
+ BASE_DIR = "#{Dir.pwd}/#{monitor_name.gsub('.', '_')}"
13
+ FileUtils.mkpath "\#{BASE_DIR}/log"
14
+
15
+ Itrp::Export::Monitor.configure do |export|
16
+ export.root = BASE_DIR
17
+ export.logger = Logger.new("\#{BASE_DIR}/log/#{monitor_name}.log")
18
+ export.ids = [#{args.export_id}]
19
+ export.unzip = true
20
+ export.sub_dirs = false
21
+
22
+ export.to = '/tmp/exports'
23
+ # export.to_ftp = 'ftp.mycompany.com'
24
+ # export.to_ftp_dir = 'my/exports'
25
+ # export.ftp_user_name = 'user'
26
+ # export.ftp_password = 'secret'
27
+
28
+ export.imap_address = 'imap.googlemail.com'
29
+ export.imap_port = 993
30
+ export.imap_user_name = '#{args.email_address}'
31
+ export.imap_password = '#{args.imap_password}'
32
+ export.imap_ssl = true
33
+ export.imap_mailbox = 'INBOX'
34
+ export.imap_archive = '[Gmail]/All Mail'
35
+ end
36
+
37
+ Itrp::Export::Monitor.run
38
+ EOF
39
+ $stdout.puts "\nGenerated '#{Dir.pwd}/#{monitor_name}.rb'."
40
+ $stdout.puts "\nEdit the file and:"
41
+ $stdout.puts " - fill in the IMAP details to connect to the mailbox that receives the ITRP Export mails"
42
+ $stdout.puts " - specify the directory or FTP server to sent the export files to"
43
+ $stdout.puts "\nStart the export monitor as follows:"
44
+ $stdout.puts " $ bundle exec ruby #{monitor_name}.rb"
45
+ $stdout.puts "\nFor more information and all available options look at the itrp-export-monitor gem documentation online.\n\n"
46
+ end
47
+
48
+ #desc %(List all scheduled exports:\n itrp-export-monitor list['<api-token>'])
49
+ #task :list, [:api_token] do |t, args|
50
+ # check_required_args(args, [:api_token])
51
+ # $stdout.puts "Searching for scheduled exports..."
52
+ # Itrp::Client.new(api_token: args.api_token).each('/exports')
53
+ # $stdout.puts "Account #{args.name} created successfully. The login instructions for #{account.url} are mailed to #{args.primary_email}."
54
+ #end
55
+
56
+ # Helper methods
57
+
58
+ # test if required arguments are provided
59
+ def check_required_args(args, fields, help)
60
+ missing = fields.select{|field| args.send(field) == nil || args.send(field) == '' }
61
+ unless missing.empty?
62
+ $stderr.puts "\n#{help}"
63
+ $stderr.puts "\nMissing required arguments: #{missing.join(', ')}"
64
+ exit(1)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,7 @@
1
+ module Itrp
2
+ module Export
3
+ module Monitor
4
+ VERSION = "1.0.0"
5
+ end
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,213 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: itrp-export-monitor
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - ITRP
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gem_config
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: itrp-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: active_support
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubyzip
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: clacks
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: 1.0.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: 1.0.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: i18n
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: bundler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: '1.3'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: '1.3'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: webmock
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ! '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ! '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: simplecov
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ! '>='
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ! '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: Monitor a mailbox and store Scheduled ITRP Exports to disk or FTP.
168
+ email: developers@itrp.com
169
+ executables:
170
+ - itrp-export-monitor
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - lib/itrp/export/monitor.rb
175
+ - lib/itrp/export/monitor/service.rb
176
+ - lib/itrp/export/monitor/version.rb
177
+ - lib/itrp/export/monitor/mail.rb
178
+ - lib/itrp/export/monitor/tasks/itrp_export_monitor.rake
179
+ - lib/itrp/export/monitor/exchange.rb
180
+ - LICENSE.txt
181
+ - README.md
182
+ - Gemfile
183
+ - Gemfile.lock
184
+ - itrp-export-monitor.gemspec
185
+ - bin/itrp-export-monitor
186
+ homepage: https://github.com/itrp/itrp-export-monitor
187
+ licenses:
188
+ - MIT
189
+ metadata: {}
190
+ post_install_message:
191
+ rdoc_options:
192
+ - --charset=UTF-8
193
+ require_paths:
194
+ - lib
195
+ required_ruby_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ! '>='
198
+ - !ruby/object:Gem::Version
199
+ version: 1.9.3
200
+ required_rubygems_version: !ruby/object:Gem::Requirement
201
+ requirements:
202
+ - - ! '>='
203
+ - !ruby/object:Gem::Version
204
+ version: '0'
205
+ requirements: []
206
+ rubyforge_project:
207
+ rubygems_version: 2.1.11
208
+ signing_key:
209
+ specification_version: 4
210
+ summary: The itrp-export-monitor gem makes it easy to monitor a mailbox receiving
211
+ Scheduled Exports from ITRP and to store the incoming export files on disk or forward
212
+ it to an FTP server.
213
+ test_files: []