clacks 1.0.4

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
+ NTYyNjIzMjk1NzcxNjcxMDAwYWExMDAzYzJhZGJlMjBjOTZlM2YyNg==
5
+ data.tar.gz: !binary |-
6
+ N2NmMjk0Yjk3OWEwZWMxZmIyMGNiMTRjZDM1ZTA1MTFiN2NmNmRiOA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NTFlMzIyYjMzMWU4OTY1MGY3ODU3NmFjZDQxNmU2MGRjYjRlMTNkY2ZmNDg5
10
+ MGE1Y2RiY2JlM2NhOTMxN2ZiNWQyZjBkYzNmNDZmNzhhNzliZjIyMmI5MDhk
11
+ ZTg4NzA1NjYwMzVkMTc1MTRiZmIxNWYyMjgyZThkMmQwOTU1MDk=
12
+ data.tar.gz: !binary |-
13
+ OTJiMjBjYzliODA0Y2QzZWU5NWFhZTU2N2ZjODc4ODEyYTY1ZWMwODZiNTk1
14
+ M2YxODZkOWFkMWVhMmYxMDEyY2ZhNDM3NmU5NjBhZjZkNWYzODNiZTU2ZDMy
15
+ YjQ4Mjg2MDNkYTcxN2U5NTliYzMzYTQ4M2I3NGYzNzUxZDYyOWY=
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 ITRP (http://developer.itrp.com)
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,104 @@
1
+ # Clacks
2
+
3
+ [![Build Status](https://secure.travis-ci.org/itrp/clacks.png)](http://travis-ci.org/itrp/clacks?branch=master)
4
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/itrp/clacks)
5
+ [![Gem Version](https://fury-badge.herokuapp.com/rb/clacks.png)](http://badge.fury.io/rb/clacks)
6
+
7
+ "The clacks is a system of shutter semaphore towers which occupies roughly the same cultural space as telegraphy in nineteenth century Europe." ^[[1]](http://en.wikipedia.org/wiki/Technology_of_the_Discworld#The_clacks)
8
+
9
+ Clacks is an easy way to process incoming emails in ruby. It uses the POP3 or IMAP protocol. If the IMAP protocol is used and the IMAP server advertises the [IDLE](http://tools.ietf.org/rfc/rfc2177.txt) capability it will use that. Which means that emails are pushed to your email processor instead of having to poll for it at regular intervals, which in turn means emails arrive near real-time at your systems.
10
+
11
+ Clacks can be used standalone and/or within a Rails environment.
12
+
13
+
14
+ Installation and Usage
15
+ ----------------------
16
+
17
+ If you use Rails, add this to your Gemfile:
18
+
19
+ gem 'clacks', :require => nil
20
+
21
+ Then create a configuration file, using ruby syntax, such as:
22
+
23
+ ``` ruby
24
+ # -*- encoding: binary -*-
25
+ # Configuration of clacks
26
+ # See Clacks::Configurator for documentation on options
27
+ #
28
+ # Put this in: <RAILS_ROOT>/config/clacks.rb
29
+ #
30
+
31
+ poll_interval 30
32
+ pid "tmp/pids/clacks.pid"
33
+ stdout_path 'log/clacks.log'
34
+ stderr_path 'log/clacks.log'
35
+
36
+ imap({
37
+ :address => "imap.googlemail.com",
38
+ :port => 993,
39
+ :user_name => '<user_name>'
40
+ :password => '<password>'
41
+ :enable_ssl => true,
42
+ })
43
+
44
+ find_options({
45
+ :mailbox => 'INBOX',
46
+ :archivebox => '[Gmail]/All Mail',
47
+ :delete_after_find => true
48
+ })
49
+
50
+ on_mail do |mail|
51
+ Clacks.logger.info "Got new mail from #{mail.from.first}, subject: #{mail.subject}"
52
+
53
+ to = mail.to.first
54
+ if to =~ /^task-(\d+)@example.com/
55
+ Task.find($1).add_note(mail)
56
+ elsif to =~ /^(\w+)@example.com/
57
+ Account.find_by_name($1).tickets.create_from_mail!(mail)
58
+ else
59
+ # Prevent deletion of this mail after all
60
+ mail.skip_deletion
61
+ end
62
+ end
63
+ ```
64
+
65
+ See [Clacks::Configurator](https://github.com/itrp/clacks/tree/master/lib/clacks/configurator.rb) for documentation on all options.
66
+
67
+ Start clacks:
68
+
69
+ ```
70
+ /project/my_rails_app$ clacks --help
71
+ /project/my_rails_app$ clacks
72
+ ```
73
+
74
+ Clacks can run as a daemon:
75
+
76
+ ```
77
+ /project/my_rails_app$ clacks -D
78
+ ```
79
+
80
+ Once it's running as a daemon process you can control it via sending signals. See the available signals below.
81
+
82
+ See the [contrib](https://github.com/itrp/clacks/tree/master/contrib/) directory for handy init.d, logrotate and monit scripts.
83
+
84
+
85
+ Signals
86
+ -------
87
+
88
+ * INT/TERM - quick shutdown, kills the process immediately.
89
+
90
+ * QUIT - graceful shutdown, waits for the worker process to finish processing an email.
91
+
92
+ * USR1 - reopen logs
93
+
94
+
95
+ Author
96
+ ----------
97
+
98
+ ITRP, mathijs.sterk@itrp.com, [developer.itrp.com](http://developer.itrp.com)
99
+
100
+
101
+ Copyright
102
+ -----------
103
+
104
+ Copyright (c) 2013 ITRP. See MIT-LICENSE for details.
data/bin/clacks ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'clacks'))
3
+ Clacks::Command.new(ARGV).exec
data/clacks.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "clacks/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{clacks}
7
+ s.version = Clacks::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["ITRP"]
10
+ s.email = %q{mathijs.sterk@itrp.com}
11
+ s.homepage = %q{http://github.com/itrp/clacks}
12
+ s.summary = %q{Clacks system for receiving emails}
13
+ s.description = %q{Clacks system for receiving emails to be processed in ruby}
14
+ s.date = Time.now.utc.strftime("%Y-%m-%d")
15
+ s.files = Dir.glob("lib/**/*") + [
16
+ "MIT-LICENSE",
17
+ "README.md",
18
+ "Gemfile",
19
+ "clacks.gemspec"
20
+ ]
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+ s.rdoc_options = ["--charset=UTF-8"]
25
+ s.add_dependency('mail')
26
+ s.add_development_dependency "rake"
27
+ # s.add_development_dependency "rcov"
28
+ s.add_development_dependency "rspec"
29
+ end
30
+
@@ -0,0 +1,171 @@
1
+ # -*- encoding: binary -*-
2
+ module Clacks
3
+ class Command
4
+ require 'optparse'
5
+
6
+ PROC_NAME = ::File.basename($0.dup)
7
+ PROC_ARGV = ARGV.map { |a| a.dup }
8
+
9
+ def initialize(args)
10
+ @options = { :config_file => "config/clacks.rb" }
11
+
12
+ opts = OptionParser.new do |opts|
13
+ opts.banner = "Usage: #{PROC_NAME} [options]"
14
+
15
+ if Clacks.rails_env?
16
+ opts.separator "Rails options:"
17
+ opts.on("-E", "--env RAILS_ENV", "use RAILS_ENV for defaults (default: development)") do |e|
18
+ ENV['RAILS_ENV'] = e
19
+ end
20
+ end
21
+
22
+ opts.separator "Ruby options:"
23
+
24
+ opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") do
25
+ $DEBUG = true
26
+ end
27
+
28
+ opts.on("-w", "--warn", "turn warnings on for your script") do
29
+ $-w = true
30
+ end
31
+
32
+ opts.separator "Clacks options:"
33
+
34
+ opts.on("-c", "--config-file FILE", "Clacks-specific config file (default: #{@options[:config_file]})") do |f|
35
+ @options[:config_file] = f
36
+ end
37
+
38
+ opts.on("-D", "--daemonize", "run daemonized in the background") do |d|
39
+ @options[:daemonize] = !!d
40
+ end
41
+
42
+ opts.on("-P", "--pid FILE", "file to store PID (default: clacks.pid)") { |f|
43
+ @options[:pid] = f
44
+ }
45
+
46
+ opts.separator "Common options:"
47
+
48
+ opts.on_tail("-h", "--help", "Show this message") do
49
+ puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '')
50
+ exit
51
+ end
52
+
53
+ opts.on_tail("-v", "--version", "Show version") do
54
+ puts "#{PROC_NAME} v#{Clacks::VERSION}"
55
+ exit
56
+ end
57
+ end
58
+ @args = opts.parse!(args)
59
+ end
60
+
61
+ def exec
62
+ daemonize if @options[:daemonize]
63
+
64
+ Clacks.require_rails if Clacks.rails_env?
65
+
66
+ Clacks.config = config = Clacks::Configurator.new(@options[:config_file])
67
+ unless config[:pop3] || config[:imap]
68
+ $stderr.puts "Either a POP3 or an IMAP server must be configured"
69
+ exit!(1)
70
+ end
71
+
72
+ reopen_io($stdout, config[:stdout_path])
73
+ reopen_io($stderr, config[:stderr_path])
74
+
75
+ unless config[:pid]
76
+ config.pid(@options[:pid] || (defined?(Rails) && Rails.version =~ /^2/ ? 'tmp/pids/clacks.pid' : 'clacks.pid'))
77
+ end
78
+ pid = config[:pid]
79
+ if wpid = running?(pid)
80
+ $stderr.puts "#{Clacks::Command::PROC_NAME} already running with pid: #{wpid} (or stale #{pid})"
81
+ exit!(1)
82
+ end
83
+ write_pid(pid)
84
+
85
+ proc_name('master')
86
+
87
+ setup_signal_handling
88
+
89
+ @service = Clacks::Service.new
90
+ @service.run
91
+ end
92
+
93
+ private
94
+
95
+ # See Stevens's "Advanced Programming in the UNIX Environment" chapter 13
96
+ def daemonize(safe = true)
97
+ $stdin.reopen '/dev/null'
98
+
99
+ # Fork and have the parent exit.
100
+ # This makes the shell or boot script think the command is done.
101
+ # Also, the child process is guaranteed not to be a process group
102
+ # leader (a prerequisite for setsid next)
103
+ exit if fork
104
+
105
+ # Call setsid to create a new session. This does three things:
106
+ # - The process becomes a session leader of a new session
107
+ # - The process becomes the process group leader of a new process group
108
+ # - The process has no controlling terminal
109
+ Process.setsid
110
+
111
+ # Fork again and have the parent exit.
112
+ # This guarantes that the daemon is not a session leader nor can
113
+ # it acquire a controlling terminal (under SVR4)
114
+ exit if fork
115
+
116
+ unless safe
117
+ ::Dir.chdir('/')
118
+ ::File.umask(0000)
119
+ end
120
+
121
+ cfg_defaults = Clacks::Configurator::DEFAULTS
122
+ cfg_defaults[:stdout_path] ||= "/dev/null"
123
+ cfg_defaults[:stderr_path] ||= "/dev/null"
124
+ end
125
+
126
+ # Redirect file descriptors inherited from the parent.
127
+ def reopen_io(io, path)
128
+ io.reopen(::File.open(path, "ab")) if path
129
+ io.sync = true
130
+ end
131
+
132
+ # Read the working pid from the pid file.
133
+ def running?(path)
134
+ wpid = ::File.read(path).to_i
135
+ return if wpid <= 0
136
+ Process.kill(0, wpid)
137
+ wpid
138
+ rescue Errno::EPERM, Errno::ESRCH, Errno::ENOENT
139
+ # noop
140
+ end
141
+
142
+ # Write the pid.
143
+ def write_pid(pid)
144
+ ::File.open(pid, 'w') { |f| f.write("#{Process.pid}") }
145
+ at_exit { ::File.delete(pid) if ::File.exist?(pid) rescue nil }
146
+ end
147
+
148
+ def proc_name(tag)
149
+ $0 = [ Clacks::Command::PROC_NAME, tag, Clacks::Command::PROC_ARGV ].join(' ')
150
+ end
151
+
152
+ def setup_signal_handling
153
+ stop_signal = (Signal.list.keys & ['QUIT', 'INT']).first
154
+ Signal.trap(stop_signal) do
155
+ Clacks.logger.info 'QUIT signal received. Shutting down gracefully.'
156
+ @service.stop if @service
157
+ end unless stop_signal.nil?
158
+
159
+ Signal.trap('USR1') do
160
+ Clacks.logger.info 'USR1 signal received. Rotating logs.'
161
+ rotate_logs
162
+ end if Signal.list['USR1']
163
+ end
164
+
165
+ def rotate_logs
166
+ reopen_io($stdout, Clacks.config[:stdout_path])
167
+ reopen_io($stderr, Clacks.config[:stderr_path])
168
+ end
169
+
170
+ end
171
+ end
@@ -0,0 +1,95 @@
1
+ # -*- encoding: binary -*-
2
+ module Clacks
3
+ class Configurator
4
+ require 'logger'
5
+ attr_accessor :map, :config_file
6
+
7
+ DEFAULTS = {
8
+ :poll_interval => 60,
9
+ :logger => Logger.new($stderr),
10
+ :on_mail => lambda { |mail|
11
+ Clacks.logger.info("Mail from #{mail.from.first}, subject: #{mail.subject}")
12
+ }
13
+ }
14
+
15
+ def initialize(config_file = nil)
16
+ self.map = Hash.new
17
+ map.merge!(DEFAULTS)
18
+ self.config_file = config_file
19
+ instance_eval(File.read(config_file), config_file) if config_file
20
+ end
21
+
22
+ def [](key) # :nodoc:
23
+ map[key]
24
+ end
25
+
26
+ def poll_interval(value)
27
+ map[:poll_interval] = value.to_i
28
+ end
29
+
30
+ def pid(path)
31
+ set_path(:pid, path)
32
+ end
33
+
34
+ # Sets the Logger-like object.
35
+ # The default Logger will log its output to Rails.logger if
36
+ # you're running within a rails environment, otherwise it will
37
+ # output to the path specified by +stdout_path+.
38
+ def logger(obj)
39
+ %w(debug info warn error fatal level).each do |m|
40
+ next if obj.respond_to?(m)
41
+ raise ArgumentError, "logger #{obj} does not respond to method #{m}"
42
+ end
43
+ map[:logger] = obj
44
+ end
45
+
46
+ # If you're running Clacks daemonized, then you must specify a path
47
+ # to prevent error messages from going to /dev/null.
48
+ def stdout_path(path)
49
+ set_path(:stdout_path, path)
50
+ end
51
+
52
+ # If you're running Clacks daemonized, then you must specify a path
53
+ # to prevent error messages from going to /dev/null.
54
+ def stderr_path(path)
55
+ set_path(:stderr_path, path)
56
+ end
57
+
58
+ def pop3(hash)
59
+ set_hash(:pop3, hash)
60
+ end
61
+
62
+ def imap(hash)
63
+ set_hash(:imap, hash)
64
+ end
65
+
66
+ def find_options(hash)
67
+ set_hash(:find_options, hash)
68
+ end
69
+
70
+ def on_mail(*args, &block)
71
+ set_hook(:on_mail, block_given? ? block : args[0])
72
+ end
73
+
74
+ private
75
+
76
+ def set_path(var, path) #:nodoc:
77
+ raise ArgumentError unless path.nil? || path.is_a?(String)
78
+ map[var] = path ? ::File.expand_path(path) : nil
79
+ end
80
+
81
+ def set_hash(var, hash) #:nodoc:
82
+ raise ArgumentError unless hash.is_a?(Hash)
83
+ map[var] = hash
84
+ end
85
+
86
+ def set_hook(var, proc) #:nodoc:
87
+ raise ArgumentError unless proc.is_a?(Proc)
88
+ unless proc.arity == 1
89
+ raise ArgumentError, "#{var}=#{proc.inspect} has invalid arity: #{proc.arity} (need 1)"
90
+ end
91
+ map[var] = proc
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,223 @@
1
+ # -*- encoding: binary -*-
2
+ module Clacks
3
+ class Service
4
+ require 'mail'
5
+
6
+ # In practice timeouts occur when there is no activity keeping an IMAP connection open.
7
+ # Timeouts occuring are:
8
+ # IMAP server timeout: typically after 30 minutes with no activity.
9
+ # NAT Gateway timeout: typically after 15 minutes with an idle connection.
10
+ # The solution to this is for the IMAP client to issue a NOOP (No Operation) command
11
+ # at intervals, typically every 15 minutes.
12
+ IMAP_NOOP_SLEEP = 15 * 60 # 15 minutes
13
+
14
+ def run
15
+ Clacks.logger.info "Clacks v#{Clacks::VERSION} started"
16
+ if Clacks.config[:pop3]
17
+ run_pop3
18
+ elsif Clacks.config[:imap]
19
+ run_imap
20
+ else
21
+ raise "Either a POP3 or an IMAP server must be configured"
22
+ end
23
+ end
24
+
25
+ def stop
26
+ $STOPPING = true
27
+ exit unless finding?
28
+ end
29
+
30
+ private
31
+
32
+ def run_pop3
33
+ config = Clacks.config[:pop3]
34
+ Clacks.logger.info("Clacks POP3 polling #{config[:user_name]}@#{config[:address]}")
35
+ # TODO: if $DEBUG
36
+ processor = Mail::IMAP.new(config)
37
+ poll(processor)
38
+ end
39
+
40
+ def run_imap
41
+ config = Clacks.config[:imap]
42
+ options = Clacks.config[:find_options]
43
+ processor = Mail::IMAP.new(config)
44
+ require 'clacks/stdlib_extensions/ruby_1_8' if RUBY_VERSION.to_f < 1.9
45
+ Net::IMAP.debug = $DEBUG
46
+ imap_validate_options(options)
47
+ if imap_idle_support?(processor)
48
+ Clacks.logger.info("Clacks IMAP idling #{config[:user_name]}@#{config[:address]}")
49
+ imap_idling(processor)
50
+ else
51
+ Clacks.logger.info("Clacks IMAP polling #{config[:user_name]}@#{config[:address]}")
52
+ poll(processor)
53
+ end
54
+ end
55
+
56
+ # Follows mostly the defaults from the Mail gem
57
+ def imap_validate_options(options)
58
+ options ||= {}
59
+ options[:mailbox] ||= 'INBOX'
60
+ options[:count] ||= 5
61
+ options[:order] ||= :asc
62
+ options[:what] ||= :first
63
+ options[:keys] ||= 'ALL'
64
+ options[:delete_after_find] ||= false
65
+ options[:mailbox] = Net::IMAP.encode_utf7(options[:mailbox])
66
+ if options[:archivebox]
67
+ options[:archivebox] = Net::IMAP.encode_utf7(options[:archivebox])
68
+ end
69
+ options
70
+ end
71
+
72
+ def imap_idle_support?(processor)
73
+ processor.connection { |imap| imap.capability.include?("IDLE") }
74
+ end
75
+
76
+ def imap_idling(processor)
77
+ imap_nooper
78
+ loop do
79
+ begin
80
+ processor.connection do |imap|
81
+ @imap = imap
82
+ # select the mailbox to process
83
+ imap.select(Clacks.config[:find_options][:mailbox])
84
+ loop {
85
+ break if stopping?
86
+ finding { imap_find(imap) }
87
+ # http://tools.ietf.org/rfc/rfc2177.txt
88
+ imap.idle do |r|
89
+ if r.instance_of?(Net::IMAP::UntaggedResponse) && r.name == 'EXISTS'
90
+ imap.idle_done unless r.data == 0
91
+ elsif r.instance_of?(Net::IMAP::ContinuationRequest)
92
+ Clacks.logger.info(r.data.text)
93
+ end
94
+ end
95
+ }
96
+ end
97
+ rescue Net::IMAP::BadResponseError => e
98
+ unless e.message == 'Could not parse command'
99
+ Clacks.logger.error("#{e.message} (#{e.class})\n#{(e.backtrace || []).join("\n")}")
100
+ end
101
+ # reconnect in next loop
102
+ rescue Net::IMAP::Error, IOError => e
103
+ # OK: reconnect in next loop
104
+ rescue => e
105
+ Clacks.logger.error("#{e.message} (#{e.class})\n#{(e.backtrace || []).join("\n")}")
106
+ sleep(5) unless stopping?
107
+ end
108
+ break if stopping?
109
+ end
110
+ end
111
+
112
+ def imap_nooper
113
+ @imap_nooper = Thread.new do
114
+ loop do
115
+ begin
116
+ sleep IMAP_NOOP_SLEEP
117
+ @imap.idle_done
118
+ @imap.noop
119
+ rescue
120
+ # noop
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ # Keep processing emails until nothing is found anymore,
127
+ # or until a QUIT signal is received to stop the process.
128
+ def imap_find(imap)
129
+ options = Clacks.config[:find_options]
130
+ begin
131
+ break if stopping?
132
+ uids = imap.uid_search(options[:keys] || 'ALL')
133
+ uids.reverse! if options[:what].to_sym == :last
134
+ uids = uids.first(options[:count]) if options[:count].is_a?(Integer)
135
+ uids.reverse! if (options[:what].to_sym == :last && options[:order].to_sym == :asc) ||
136
+ (options[:what].to_sym != :last && options[:order].to_sym == :desc)
137
+ processed = 0
138
+ uids.each do |uid|
139
+ break if stopping?
140
+ source = imap.uid_fetch(uid, ['RFC822']).first.attr['RFC822']
141
+ break if stopping?
142
+ mail = Mail.new(source)
143
+ mail.mark_for_delete = true if options[:delete_after_find]
144
+ begin
145
+ Clacks.config[:on_mail].call(mail)
146
+ rescue Exception => e
147
+ Clacks.logger.debug(e.message)
148
+ Clacks.logger.debug(e.backtrace)
149
+ end
150
+ begin
151
+ imap.uid_copy(uid, options[:archivebox]) if options[:archivebox]
152
+ if options[:delete_after_find] && mail.is_marked_for_delete?
153
+ imap.uid_store(uid, "+FLAGS", [Net::IMAP::DELETED])
154
+ end
155
+ rescue Exception => e
156
+ Clacks.logger.error(e.message)
157
+ end
158
+ processed += 1
159
+ end
160
+ imap.expunge if options[:delete_after_find]
161
+ end while uids.any? && processed == uids.length
162
+ end
163
+
164
+ def poll(processor)
165
+ polling_msg = if polling?
166
+ "Clacks polling every #{poll_interval} seconds."
167
+ else
168
+ "Clacks polling for messages once."
169
+ end
170
+ Clacks.logger.info(polling_msg)
171
+
172
+ find_options = Clacks.config[:find_options]
173
+ on_mail = Clacks.config[:on_mail]
174
+ loop do
175
+ break if stopping?
176
+ finding {
177
+ processor.find(find_options) do |mail|
178
+ if stopping?
179
+ mail.skip_deletion
180
+ else
181
+ begin
182
+ on_mail.call(mail)
183
+ rescue Exception => e
184
+ Clacks.logger.debug(e.message)
185
+ Clacks.logger.debug(e.backtrace)
186
+ end
187
+ end
188
+ end
189
+ }
190
+ break if stopping? || !polling?
191
+ sleep(poll_interval)
192
+ end
193
+ end
194
+
195
+ def poll_interval
196
+ Clacks.config[:poll_interval]
197
+ end
198
+
199
+ def polling?
200
+ poll_interval > 0
201
+ end
202
+
203
+ def finding(&block)
204
+ @finding = true
205
+ yield
206
+ ensure
207
+ @finding = false
208
+ end
209
+
210
+ def finding?
211
+ @finding
212
+ end
213
+
214
+ def stopping?
215
+ $STOPPING
216
+ end
217
+
218
+ at_exit {
219
+ Clacks.logger.info("Clacks stopped.") if $STOPPING
220
+ }
221
+
222
+ end
223
+ end
@@ -0,0 +1,41 @@
1
+ # -*- encoding: binary -*-
2
+ # Backport from ruby 1.9.3 source to ruby 1.8.7
3
+ class Net::IMAP
4
+ def idle(&response_handler)
5
+ raise LocalJumpError, "no block given" unless response_handler
6
+
7
+ response = nil
8
+
9
+ synchronize do
10
+ tag = Thread.current[:net_imap_tag] = generate_tag
11
+ put_string("#{tag} IDLE#{CRLF}")
12
+
13
+ begin
14
+ add_response_handler(response_handler)
15
+ @idle_done_cond = new_cond
16
+ @idle_done_cond.wait
17
+ @idle_done_cond = nil
18
+ if @receiver_thread_terminating
19
+ raise Net::IMAP::Error, "connection closed"
20
+ end
21
+ ensure
22
+ unless @receiver_thread_terminating
23
+ remove_response_handler(response_handler)
24
+ put_string("DONE#{CRLF}")
25
+ response = get_tagged_response(tag) #, "IDLE")
26
+ end
27
+ end
28
+ end
29
+
30
+ return response
31
+ end
32
+
33
+ def idle_done
34
+ synchronize do
35
+ if @idle_done_cond.nil?
36
+ raise Net::IMAP::Error, "not during IDLE"
37
+ end
38
+ @idle_done_cond.signal
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,4 @@
1
+ # -*- encoding: binary -*-
2
+ module Clacks
3
+ VERSION = '1.0.4'
4
+ end
data/lib/clacks.rb ADDED
@@ -0,0 +1,30 @@
1
+ # -*- encoding: binary -*-
2
+ module Clacks
3
+ require 'clacks/version'
4
+ require 'clacks/configurator'
5
+ require 'clacks/command'
6
+ require 'clacks/service'
7
+
8
+ def self.config=(config)
9
+ @config = config
10
+ end
11
+
12
+ def self.config
13
+ @config ||= Clacks::Configurator.new
14
+ end
15
+
16
+ def self.logger
17
+ @logger ||= Clacks.config[:logger]
18
+ end
19
+
20
+ RAILS_CONFIG_ENV = 'config/environment.rb'
21
+ def self.rails_env?
22
+ @rails_env ||= defined?(Rails) || File.readable?(RAILS_CONFIG_ENV)
23
+ end
24
+
25
+ def self.require_rails
26
+ ENV['RAILS_ENV'] ||= 'development'
27
+ require "#{Dir.pwd}/#{RAILS_CONFIG_ENV}" unless defined?(Rails)
28
+ end
29
+ end
30
+
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clacks
3
+ version: !ruby/object:Gem::Version
4
+ version: !binary |-
5
+ MS4wLjQ=
6
+ platform: ruby
7
+ authors:
8
+ - ITRP
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mail
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ! '>='
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ! '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ description: Clacks system for receiving emails to be processed in ruby
57
+ email: mathijs.sterk@itrp.com
58
+ executables:
59
+ - clacks
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - lib/clacks/service.rb
64
+ - lib/clacks/version.rb
65
+ - lib/clacks/stdlib_extensions/ruby_1_8.rb
66
+ - lib/clacks/configurator.rb
67
+ - lib/clacks/command.rb
68
+ - lib/clacks.rb
69
+ - MIT-LICENSE
70
+ - README.md
71
+ - Gemfile
72
+ - clacks.gemspec
73
+ - bin/clacks
74
+ homepage: http://github.com/itrp/clacks
75
+ licenses: []
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options:
79
+ - --charset=UTF-8
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.0.3
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: Clacks system for receiving emails
98
+ test_files: []