itrp-export-monitor 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []