activewarehouse-etl 0.9.1 → 0.9.5.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. data/.gitignore +7 -0
  2. data/0.9-UPGRADE +6 -0
  3. data/CHANGELOG +182 -150
  4. data/Gemfile +4 -0
  5. data/HOW_TO_RELEASE +9 -0
  6. data/README +18 -2
  7. data/Rakefile +35 -91
  8. data/active_support_logger.patch +78 -0
  9. data/activewarehouse-etl.gemspec +30 -0
  10. data/lib/etl.rb +10 -2
  11. data/lib/etl/batch/directives.rb +11 -1
  12. data/lib/etl/control/control.rb +2 -2
  13. data/lib/etl/control/destination.rb +27 -7
  14. data/lib/etl/control/destination/database_destination.rb +8 -6
  15. data/lib/etl/control/destination/excel_destination.rb +91 -0
  16. data/lib/etl/control/destination/file_destination.rb +6 -4
  17. data/lib/etl/control/destination/insert_update_database_destination.rb +133 -0
  18. data/lib/etl/control/destination/update_database_destination.rb +109 -0
  19. data/lib/etl/control/source.rb +3 -2
  20. data/lib/etl/control/source/database_source.rb +14 -10
  21. data/lib/etl/control/source/file_source.rb +2 -2
  22. data/lib/etl/engine.rb +17 -15
  23. data/lib/etl/execution.rb +0 -1
  24. data/lib/etl/execution/batch.rb +3 -1
  25. data/lib/etl/execution/migration.rb +5 -0
  26. data/lib/etl/parser/delimited_parser.rb +20 -1
  27. data/lib/etl/parser/excel_parser.rb +112 -0
  28. data/lib/etl/processor/bulk_import_processor.rb +4 -2
  29. data/lib/etl/processor/database_join_processor.rb +68 -0
  30. data/lib/etl/processor/escape_csv_processor.rb +77 -0
  31. data/lib/etl/processor/filter_row_processor.rb +51 -0
  32. data/lib/etl/processor/ftp_downloader_processor.rb +68 -0
  33. data/lib/etl/processor/ftp_uploader_processor.rb +65 -0
  34. data/lib/etl/processor/imapattachment_downloader_processor.rb +91 -0
  35. data/lib/etl/processor/pop3attachment_downloader_processor.rb +90 -0
  36. data/lib/etl/processor/sftp_downloader_processor.rb +63 -0
  37. data/lib/etl/processor/sftp_uploader_processor.rb +63 -0
  38. data/lib/etl/processor/zip_file_processor.rb +27 -0
  39. data/lib/etl/transform/calculation_transform.rb +71 -0
  40. data/lib/etl/transform/foreign_key_lookup_transform.rb +25 -7
  41. data/lib/etl/transform/ordinalize_transform.rb +3 -1
  42. data/lib/etl/transform/split_fields_transform.rb +27 -0
  43. data/lib/etl/version.rb +1 -7
  44. data/test-matrix.yml +10 -0
  45. data/test/.gitignore +1 -0
  46. data/test/.ignore +2 -0
  47. data/test/all.ebf +6 -0
  48. data/test/apache_combined_log.ctl +11 -0
  49. data/test/batch_test.rb +41 -0
  50. data/test/batch_with_error.ebf +6 -0
  51. data/test/batched1.ctl +0 -0
  52. data/test/batched2.ctl +0 -0
  53. data/test/block_processor.ctl +6 -0
  54. data/test/block_processor_error.ctl +1 -0
  55. data/test/block_processor_pre_post_process.ctl +4 -0
  56. data/test/block_processor_remove_rows.ctl +5 -0
  57. data/test/block_processor_test.rb +38 -0
  58. data/test/config/Gemfile.rails-2.3.x +3 -0
  59. data/test/config/Gemfile.rails-2.3.x.lock +38 -0
  60. data/test/config/Gemfile.rails-3.0.x +3 -0
  61. data/test/config/Gemfile.rails-3.0.x.lock +49 -0
  62. data/test/config/common.rb +21 -0
  63. data/test/connection/mysql/connection.rb +9 -0
  64. data/test/connection/mysql/schema.sql +36 -0
  65. data/test/connection/postgresql/connection.rb +13 -0
  66. data/test/connection/postgresql/schema.sql +39 -0
  67. data/test/control_test.rb +43 -0
  68. data/test/data/apache_combined_log.txt +3 -0
  69. data/test/data/bulk_import.txt +3 -0
  70. data/test/data/bulk_import_with_empties.txt +3 -0
  71. data/test/data/decode.txt +3 -0
  72. data/test/data/delimited.txt +3 -0
  73. data/test/data/encode_source_latin1.txt +2 -0
  74. data/test/data/excel.xls +0 -0
  75. data/test/data/excel2.xls +0 -0
  76. data/test/data/fixed_width.txt +3 -0
  77. data/test/data/multiple_delimited_1.txt +3 -0
  78. data/test/data/multiple_delimited_2.txt +3 -0
  79. data/test/data/people.txt +3 -0
  80. data/test/data/sax.xml +14 -0
  81. data/test/data/xml.xml +16 -0
  82. data/test/date_dimension_builder_test.rb +96 -0
  83. data/test/delimited.ctl +30 -0
  84. data/test/delimited_absolute.ctl +33 -0
  85. data/test/delimited_destination_db.ctl +25 -0
  86. data/test/delimited_excel.ctl +31 -0
  87. data/test/delimited_insert_update.ctl +34 -0
  88. data/test/delimited_update.ctl +34 -0
  89. data/test/delimited_with_bulk_load.ctl +34 -0
  90. data/test/destination_test.rb +275 -0
  91. data/test/directive_test.rb +23 -0
  92. data/test/encode_processor_test.rb +32 -0
  93. data/test/engine_test.rb +32 -0
  94. data/test/errors.ctl +24 -0
  95. data/test/etl_test.rb +42 -0
  96. data/test/excel.ctl +24 -0
  97. data/test/excel2.ctl +25 -0
  98. data/test/fixed_width.ctl +35 -0
  99. data/test/generator_test.rb +14 -0
  100. data/test/inline_parser.ctl +17 -0
  101. data/test/mocks/mock_destination.rb +26 -0
  102. data/test/mocks/mock_source.rb +25 -0
  103. data/test/model_source.ctl +14 -0
  104. data/test/multiple_delimited.ctl +22 -0
  105. data/test/multiple_source_delimited.ctl +39 -0
  106. data/test/parser_test.rb +224 -0
  107. data/test/performance/delimited.ctl +30 -0
  108. data/test/processor_test.rb +44 -0
  109. data/test/row_processor_test.rb +17 -0
  110. data/test/sax.ctl +26 -0
  111. data/test/scd/1.txt +1 -0
  112. data/test/scd/2.txt +1 -0
  113. data/test/scd/3.txt +1 -0
  114. data/test/scd_test.rb +257 -0
  115. data/test/scd_test_type_1.ctl +43 -0
  116. data/test/scd_test_type_2.ctl +34 -0
  117. data/test/screen_test.rb +9 -0
  118. data/test/screen_test_error.ctl +3 -0
  119. data/test/screen_test_fatal.ctl +3 -0
  120. data/test/source_test.rb +139 -0
  121. data/test/test_helper.rb +34 -0
  122. data/test/transform_test.rb +101 -0
  123. data/test/vendor/adapter_extensions-0.5.0/CHANGELOG +26 -0
  124. data/test/vendor/adapter_extensions-0.5.0/LICENSE +16 -0
  125. data/test/vendor/adapter_extensions-0.5.0/README +7 -0
  126. data/test/vendor/adapter_extensions-0.5.0/Rakefile +158 -0
  127. data/test/vendor/adapter_extensions-0.5.0/lib/adapter_extensions.rb +12 -0
  128. data/test/vendor/adapter_extensions-0.5.0/lib/adapter_extensions/connection_adapters/abstract_adapter.rb +44 -0
  129. data/test/vendor/adapter_extensions-0.5.0/lib/adapter_extensions/connection_adapters/mysql_adapter.rb +63 -0
  130. data/test/vendor/adapter_extensions-0.5.0/lib/adapter_extensions/connection_adapters/postgresql_adapter.rb +52 -0
  131. data/test/vendor/adapter_extensions-0.5.0/lib/adapter_extensions/connection_adapters/sqlserver_adapter.rb +44 -0
  132. data/test/vendor/adapter_extensions-0.5.0/lib/adapter_extensions/version.rb +10 -0
  133. data/test/xml.ctl +31 -0
  134. metadata +229 -70
  135. data/lib/etl/execution/record.rb +0 -18
@@ -38,8 +38,10 @@ module ETL #:nodoc:
38
38
  # * <tt>:field_enclosure</tt>: The field enclosure charcaters
39
39
  def initialize(control, configuration)
40
40
  super
41
- @file = File.join(File.dirname(control.file), configuration[:file])
42
41
  @target = configuration[:target]
42
+ path = Pathname.new(configuration[:file])
43
+ @file = path.absolute? ? path : Pathname.new(File.dirname(File.expand_path(control.file))) + path
44
+
43
45
  @table = configuration[:table]
44
46
  @truncate = configuration[:truncate] ||= false
45
47
  @columns = configuration[:columns]
@@ -78,4 +80,4 @@ module ETL #:nodoc:
78
80
  end
79
81
  end
80
82
  end
81
- end
83
+ end
@@ -0,0 +1,68 @@
1
+ module ETL
2
+ module Processor
3
+ class DatabaseJoinProcessor < ETL::Processor::RowProcessor
4
+ attr_reader :target
5
+ attr_reader :query
6
+ attr_reader :fields
7
+
8
+ # Initialize the procesor.
9
+ #
10
+ # Arguments:
11
+ # * <tt>control</tt>: The ETL::Control::Control instance
12
+ # * <tt>configuration</tt>: The configuration Hash
13
+ # * <tt>definition</tt>: The source definition
14
+ #
15
+ # Required configuration options:
16
+ # * <tt>:target</tt>: The target connection
17
+ # * <tt>:query</tt>: The join query
18
+ # * <tt>:fields</tt>: The fields to add to the row
19
+ def initialize(control, configuration)
20
+ super
21
+ @target = configuration[:target]
22
+ @query = configuration[:query]
23
+ @fields = configuration[:fields]
24
+ end
25
+
26
+ # Get a String identifier for the source
27
+ def to_s
28
+ "#{host}/#{database}"
29
+ end
30
+
31
+ def process(row)
32
+ return nil if row.nil?
33
+
34
+ q = @query
35
+ begin
36
+ q = eval('"' + @query + '"')
37
+ rescue
38
+ end
39
+
40
+ ETL::Engine.logger.debug("Executing select: #{q}")
41
+ res = connection.execute(q)
42
+
43
+ res.each_hash do |r|
44
+ @fields.each do |field|
45
+ row[field.to_sym] = r[field]
46
+ end
47
+ end
48
+
49
+ return row
50
+ end
51
+
52
+ private
53
+ # Get the database connection to use
54
+ def connection
55
+ ETL::Engine.connection(target)
56
+ end
57
+
58
+ # Get the host, defaults to 'localhost'
59
+ def host
60
+ ETL::Base.configurations[target.to_s]['host'] || 'localhost'
61
+ end
62
+
63
+ def database
64
+ ETL::Base.configurations[target.to_s]['database']
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,77 @@
1
+ require 'fileutils'
2
+
3
+ module ETL #:nodoc:
4
+ module Processor #:nodoc:
5
+ class EscapeCsvProcessor < ETL::Processor::Processor
6
+
7
+ # The file to load from
8
+ attr_reader :source_file
9
+ # The file to write to
10
+ attr_reader :target_file
11
+ # whether to use a temporary file or not
12
+ attr_reader :use_temp_file
13
+
14
+ attr_reader :filters
15
+ attr_reader :charcount
16
+
17
+ # Initialize the processor.
18
+ #
19
+ # Configuration options:
20
+ # * <tt>:source_file</tt>: The file to load data from
21
+ # * <tt>:target_file</tt>: The file to write data to
22
+ # * <tt>:file</tt>: short-cut which will set the same value to both source_file and target_file
23
+ def initialize(control, configuration)
24
+ super
25
+ if configuration[:file]
26
+ @use_temp_file = true
27
+ configuration[:source_file] = configuration[:file]
28
+ configuration[:target_file] = configuration[:file] + '.tmp'
29
+ end
30
+ path = Pathname.new(configuration[:source_file])
31
+ @source_file = path.absolute? ? path : Pathname.new(File.dirname(File.expand_path(configuration[:source_file]))) + path
32
+ path = Pathname.new(configuration[:target_file])
33
+ @target_file = path.absolute? ? path : Pathname.new(File.dirname(File.expand_path(configuration[:target_file]))) + path
34
+ @filters = configuration[:filters] || [{:replace => '\"', :result => '""'}]
35
+ @charcount = configuration[:charcount]
36
+ raise ControlError, "Source file must be specified" if @source_file.nil?
37
+ raise ControlError, "Target file must be specified" if @target_file.nil?
38
+ raise ControlError, "Source and target file cannot currently point to the same file" if @source_file == @target_file
39
+ end
40
+
41
+ # Execute the processor
42
+ def process
43
+ reader = File.open(@source_file, 'r')
44
+ writer = File.open(@target_file, 'w')
45
+
46
+ reader.each_line do |line|
47
+ reading = line
48
+ @filters.each do |filter|
49
+ if (!filter[:replace].nil? &&
50
+ !filter[:result].nil?)
51
+ result = reading.gsub(Regexp.new(filter[:replace]), filter[:result])
52
+ reading = result
53
+ end
54
+ end unless @filters.nil?
55
+ @charcount.each do |count|
56
+ if (!count[:char].nil? &&
57
+ !count[:count].nil?)
58
+ c = reading.count count[:char]
59
+ if c != count[:count]
60
+ reading = nil
61
+ end
62
+ end
63
+ end unless @charcount.nil?
64
+ writer.write(reading) unless reading.nil?
65
+ end
66
+
67
+ reader.close
68
+ writer.close
69
+
70
+ if use_temp_file
71
+ FileUtils.rm(source_file)
72
+ FileUtils.mv(target_file,source_file)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,51 @@
1
+ module ETL
2
+ module Processor
3
+ class FilterRowProcessor < ETL::Processor::RowProcessor
4
+ attr_reader :condition
5
+ attr_reader :outtrue
6
+ attr_reader :outfalse
7
+
8
+ def initialize(control, configuration)
9
+ @condition = configuration[:condition]
10
+ @outtrue = configuration[:outtrue]
11
+ @outfalse = configuration[:outfalse]
12
+ super
13
+ end
14
+
15
+ def process(row)
16
+ return nil if row.nil?
17
+
18
+ if eval_condition(row, @condition)
19
+ return [] if @outtrue.nil?
20
+
21
+ eval(@outtrue)
22
+ else
23
+ eval(@outfalse) unless @outfalse.nil?
24
+ end
25
+
26
+ return row
27
+ end
28
+
29
+ private
30
+ def eval_condition(row, cond)
31
+
32
+ first = cond[1]
33
+ if (cond[1].class == Array)
34
+ first = eval_condition(row, cond[1])
35
+ end
36
+
37
+ second = cond[2]
38
+ if (cond[2].class == Array)
39
+ second = eval_condition(row, cond[2])
40
+ end
41
+
42
+ return eval("#{cond[0]}#{first}#{second}") if cond[0] == "!"
43
+
44
+ eval("#{first}#{cond[0]}#{second}")
45
+ rescue => e
46
+ return false
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,68 @@
1
+ # Written by Susan Potter under open source MIT license.
2
+ # August 12, 2007.
3
+
4
+ require 'net/ftp'
5
+
6
+ module ETL
7
+ module Processor
8
+ # Custom processor to download files via FTP
9
+ class FtpDownloaderProcessor < ETL::Processor::Processor
10
+ attr_reader :host
11
+ attr_reader :port
12
+ attr_reader :remote_dir
13
+ attr_reader :files
14
+ attr_reader :username
15
+ attr_reader :local_dir
16
+
17
+ # configuration options include:
18
+ # * host - hostname or IP address of FTP server (required)
19
+ # * port - port number for FTP server (default: 21)
20
+ # * remote_dir - remote path on FTP server (default: /)
21
+ # * files - list of files to download from FTP server (default: [])
22
+ # * username - username for FTP server authentication (default: anonymous)
23
+ # * password - password for FTP server authentication (default: nil)
24
+ # * local_dir - local output directory to save downloaded files (default: '')
25
+ #
26
+ # As an example you might write something like the following in your control process file:
27
+ # pre_process :ftp_downloader, {
28
+ # :host => 'ftp.sec.gov',
29
+ # :path => 'edgar/Feed/2007/QTR2',
30
+ # :files => ['20070402.nc.tar.gz', '20070403.nc.tar.gz', '20070404.nc.tar.gz',
31
+ # '20070405.nc.tar.gz', '20070406.nc.tar.gz'],
32
+ # :local_dir => '/data/sec/2007/04',
33
+ # }
34
+ # The above example will anonymously download via FTP the first week's worth of SEC filing feed data
35
+ # from the second quarter of 2007 and download the files to the local directory +/data/sec/2007/04+.
36
+ def initialize(control, configuration)
37
+ @host = configuration[:host]
38
+ @port = configuration[:port] || 21
39
+ @remote_dir = configuration[:remote_dir] || '/'
40
+ @files = configuration[:files] || []
41
+ @username = configuration[:username] || 'anonymous'
42
+ @password = configuration[:password]
43
+ @local_dir = configuration[:local_dir] || ''
44
+ end
45
+
46
+ def process
47
+ Net::FTP.open(@host) do |conn|
48
+ conn.connect(@host, @port)
49
+ conn.login(@username, @password)
50
+ @files.each do |f|
51
+ conn.getbinaryfile(remote_file(f), local_file(f))
52
+ end
53
+ end
54
+ end
55
+
56
+ private
57
+ attr_accessor :password
58
+
59
+ def local_file(name)
60
+ File.join(@local_dir, name)
61
+ end
62
+
63
+ def remote_file(name)
64
+ File.join(@remote_dir, name)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,65 @@
1
+ require 'net/ftp'
2
+
3
+ module ETL
4
+ module Processor
5
+ # Custom processor to download files via FTP
6
+ class FtpUploaderProcessor < ETL::Processor::Processor
7
+ attr_reader :host
8
+ attr_reader :port
9
+ attr_reader :remote_dir
10
+ attr_reader :files
11
+ attr_reader :username
12
+ attr_reader :local_dir
13
+
14
+ # configuration options include:
15
+ # * host - hostname or IP address of FTP server (required)
16
+ # * port - port number for FTP server (default: 21)
17
+ # * remote_dir - remote path on FTP server (default: /)
18
+ # * files - list of files to download from FTP server (default: [])
19
+ # * username - username for FTP server authentication (default: anonymous)
20
+ # * password - password for FTP server authentication (default: nil)
21
+ # * local_dir - local output directory to save downloaded files (default: '')
22
+ #
23
+ # As an example you might write something like the following in your control process file:
24
+ # pre_process :ftp_uploader, {
25
+ # :host => 'ftp.sec.gov',
26
+ # :path => 'edgar/Feed/2007/QTR2',
27
+ # :files => ['20070402.nc.tar.gz', '20070403.nc.tar.gz', '20070404.nc.tar.gz',
28
+ # '20070405.nc.tar.gz', '20070406.nc.tar.gz'],
29
+ # :local_dir => '/data/sec/2007/04',
30
+ # }
31
+ # The above example will anonymously download via FTP the first week's worth of SEC filing feed data
32
+ # from the second quarter of 2007 and download the files to the local directory +/data/sec/2007/04+.
33
+ def initialize(control, configuration)
34
+ @host = configuration[:host]
35
+ @port = configuration[:port] || 21
36
+ @remote_dir = configuration[:remote_dir] || '/'
37
+ @files = configuration[:files] || []
38
+ @username = configuration[:username] || 'anonymous'
39
+ @password = configuration[:password]
40
+ @local_dir = configuration[:local_dir] || ''
41
+ end
42
+
43
+ def process
44
+ Net::FTP.open(@host) do |conn|
45
+ conn.connect(@host, @port)
46
+ conn.login(@username, @password)
47
+ @files.each do |f|
48
+ conn.putbinaryfile(local_file(f), remote_file(f))
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+ attr_accessor :password
55
+
56
+ def local_file(name)
57
+ File.join(@local_dir, name)
58
+ end
59
+
60
+ def remote_file(name)
61
+ File.join(@remote_dir, name)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,91 @@
1
+ require 'net/imap'
2
+ require 'tmail'
3
+
4
+ module ETL
5
+ module Processor
6
+ # Custom processor to download files via Imap Attachment
7
+ class ImapattachmentDownloaderProcessor < ETL::Processor::Processor
8
+ attr_reader :host
9
+ attr_reader :ssl
10
+ attr_reader :port
11
+ attr_reader :delete
12
+ attr_reader :filters
13
+ attr_reader :folder
14
+ attr_reader :username
15
+ attr_reader :local_dir
16
+
17
+ # configuration options include:
18
+ # * host - hostname or IP address of IMAP server (required)
19
+ # * ssl - activate encryption (default false)
20
+ # * port - port number for IMAP server (default: 220 or 993)
21
+ # * delete - delete message after reading (default false)
22
+ # * filters - filter mails (default [])
23
+ # * folder - folder to select mails from (default INBOX)
24
+ # * username - username for IMAP server authentication (default: anonymous)
25
+ # * password - password for IMAP server authentication (default: nil)
26
+ # * local_dir - local output directory to save downloaded files (default: '')
27
+ #
28
+ def initialize(control, configuration)
29
+ @host = configuration[:host]
30
+ @ssl = configuration[:ssl] || false
31
+ @port = configuration[:port] || (@ssl ? 993 : 220 )
32
+ @delete = configuration[:delete] || false
33
+ @filters = configuration[:filters] || []
34
+ @folder = configuration[:folder] || 'INBOX'
35
+ @username = configuration[:username] || 'anonymous'
36
+ @password = configuration[:password]
37
+ @local_dir = configuration[:local_dir] || ''
38
+ end
39
+
40
+ def process
41
+ conn = Net::IMAP.new(@host, @port, @ssl)
42
+ conn.login(@username, @password)
43
+
44
+ conn.select(@folder)
45
+ conn.uid_search(["NOT", "DELETED"]).each do |msguuid|
46
+ mail = TMail::Mail.parse( conn.uid_fetch(msguuid, 'RFC822').first.attr['RFC822'] )
47
+ next if mail.attachments.blank?
48
+ if applyfilter(mail, @filters)
49
+ mail.attachments.each do |attachment|
50
+ filename = attachment.original_filename
51
+ File.open(local_file(filename), "w") {|f|
52
+ f << attachment.gets(nil)
53
+ }
54
+ end
55
+
56
+ conn.store(msguuid, "+FLAGS", [:Deleted]) if @delete
57
+ end
58
+ end
59
+ conn.expunge
60
+ conn.close
61
+ end
62
+
63
+ private
64
+ attr_accessor :password
65
+
66
+ def local_file(name)
67
+ File.join(@local_dir, name)
68
+ end
69
+
70
+ def applyfilter(mail, cond)
71
+ return true if (cond.nil? or cond.size < 3)
72
+
73
+ first = cond[1]
74
+ if (cond[1].class == Array)
75
+ first = eval_condition(row, cond[1])
76
+ end
77
+
78
+ second = cond[2]
79
+ if (cond[2].class == Array)
80
+ second = eval_condition(row, cond[2])
81
+ end
82
+
83
+ return eval("#{cond[0]}#{first}#{second}") if cond[0] == "!"
84
+
85
+ eval("#{first}#{cond[0]}#{second}")
86
+ rescue => e
87
+ return false
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,90 @@
1
+ require 'net/pop'
2
+ require 'tmail'
3
+
4
+ module ETL
5
+ module Processor
6
+ # Custom processor to download files via Pop3 Attachment
7
+ class Pop3attachmentDownloaderProcessor < ETL::Processor::Processor
8
+ attr_reader :host
9
+ attr_reader :ssl
10
+ attr_reader :port
11
+ attr_reader :delete
12
+ attr_reader :filters
13
+ attr_reader :username
14
+ attr_reader :local_dir
15
+
16
+ # configuration options include:
17
+ # * host - hostname or IP address of POP3 server (required)
18
+ # * ssl - activate encryption (default false)
19
+ # * port - port number for POP3 server (default: Net::POP3.default_port or Net::POP3.default_pop3s_port)
20
+ # * delete - delete message after reading (default false)
21
+ # * filters - filter mails (default [])
22
+ # * username - username for POP3 server authentication (default: anonymous)
23
+ # * password - password for POP3 server authentication (default: nil)
24
+ # * local_dir - local output directory to save downloaded files (default: '')
25
+ #
26
+ def initialize(control, configuration)
27
+ @host = configuration[:host]
28
+ @ssl = configuration[:ssl] || false
29
+ @port = configuration[:port] || (@ssl ? Net::POP3.default_pop3s_port : Net::POP3.default_port )
30
+ @delete = configuration[:delete] || false
31
+ @filters = configuration[:filters] || []
32
+ @username = configuration[:username] || 'anonymous'
33
+ @password = configuration[:password]
34
+ @local_dir = configuration[:local_dir] || ''
35
+ end
36
+
37
+ def process
38
+ Net::POP3.enable_ssl(OpenSSL::SSL::VERIFY_NONE) if @ssl
39
+ conn = Net::POP3.new(@host, @port)
40
+ conn.start(@username, @password)
41
+ if !conn.mails.empty?
42
+ conn.each_mail do |message|
43
+ stringmail = message.pop
44
+ mail = TMail::Mail.parse(stringmail)
45
+ next if mail.attachments.blank?
46
+ if applyfilter(mail, @filters)
47
+ mail.attachments.each do |attachment|
48
+ filename = attachment.original_filename
49
+ File.open(local_file(filename), "w") {|f|
50
+ f << attachment.gets(nil)
51
+ }
52
+ end
53
+
54
+ message.delete if @delete
55
+ end
56
+ end
57
+ end
58
+
59
+ conn.finish
60
+ end
61
+
62
+ private
63
+ attr_accessor :password
64
+
65
+ def local_file(name)
66
+ File.join(@local_dir, name)
67
+ end
68
+
69
+ def applyfilter(mail, cond)
70
+ return true if (cond.nil? or cond.size < 3)
71
+
72
+ first = cond[1]
73
+ if (cond[1].class == Array)
74
+ first = eval_condition(row, cond[1])
75
+ end
76
+
77
+ second = cond[2]
78
+ if (cond[2].class == Array)
79
+ second = eval_condition(row, cond[2])
80
+ end
81
+
82
+ return eval("#{cond[0]}#{first}#{second}") if cond[0] == "!"
83
+
84
+ eval("#{first}#{cond[0]}#{second}")
85
+ rescue => e
86
+ return false
87
+ end
88
+ end
89
+ end
90
+ end