class Clacks::Service
Constants
- WATCHDOG_SLEEP
In practice timeouts occur when there is no activity keeping an IMAP connection open. Timeouts occuring are:
IMAP server timeout: typically after 30 minutes with no activity. NAT Gateway timeout: typically after 15 minutes with an idle connection.
The solution to this is for the IMAP client to issue a NOOP (No Operation) command at intervals, typically every 29 minutes. We choose default 10 minutes.
Public Instance Methods
run()
click to toggle source
# File lib/clacks/service.rb, line 16 def run begin Clacks.logger.info "Clacks v#{Clacks::VERSION} started" if Clacks.config[:pop3] run_pop3 elsif Clacks.config[:imap] run_imap else raise "Either a POP3 or an IMAP server must be configured" end rescue Exception => e fatal(e) end end
stop()
click to toggle source
# File lib/clacks/service.rb, line 31 def stop $STOPPING = true exit unless finding? end
Private Instance Methods
fatal(e)
click to toggle source
# File lib/clacks/service.rb, line 38 def fatal(e) unless e.is_a?(SystemExit) || e.is_a?(SignalException) Clacks.logger.fatal("#{e.message} (#{e.class})\n#{(e.backtrace || []).join("\n")}") end stop raise e end
finding() { || ... }
click to toggle source
# File lib/clacks/service.rb, line 232 def finding(&block) @finding = true yield ensure @finding = false end
finding?()
click to toggle source
# File lib/clacks/service.rb, line 239 def finding? @finding end
imap_find(imap)
click to toggle source
Keep processing emails until nothing is found anymore, or until a QUIT signal is received to stop the process.
# File lib/clacks/service.rb, line 154 def imap_find(imap) options = Clacks.config[:find_options] delete_after_find = options[:delete_after_find] begin break if stopping? uids = imap.uid_search(options[:keys] || 'ALL') uids.reverse! if options[:what].to_sym == :last uids = uids.first(options[:count]) if options[:count].is_a?(Integer) uids.reverse! if (options[:what].to_sym == :last && options[:order].to_sym == :asc) || (options[:what].to_sym != :last && options[:order].to_sym == :desc) processed = 0 expunge = false uids.each do |uid| break if stopping? source = imap.uid_fetch(uid, ['RFC822']).first.attr['RFC822'] mail = nil begin mail = Mail.new(source) mail.mark_for_delete = true if delete_after_find Clacks.config[:on_mail].call(mail) rescue StandardError => e Clacks.logger.error(e.message) Clacks.logger.error(e.backtrace) end begin imap.uid_copy(uid, options[:archivebox]) if options[:archivebox] if delete_after_find && (mail.nil? || mail.is_marked_for_delete?) expunge = true imap.uid_store(uid, "+FLAGS", [Net::IMAP::DELETED]) end rescue StandardError => e Clacks.logger.error(e.message) end processed += 1 end imap.expunge if expunge end while uids.any? && processed == uids.length end
imap_idle_support?(processor)
click to toggle source
# File lib/clacks/service.rb, line 88 def imap_idle_support?(processor) processor.connection { |imap| imap.capability.include?("IDLE") } end
imap_idling(processor)
click to toggle source
# File lib/clacks/service.rb, line 92 def imap_idling(processor) imap_watchdog loop do begin processor.connection do |imap| @imap = imap # select the mailbox to process imap.select(Clacks.config[:find_options][:mailbox]) loop { break if stopping? finding { imap_find(imap) } # http://tools.ietf.org/rfc/rfc2177.txt Clacks.logger.debug('imap.idle start') imap.idle do |r| Clacks.logger.debug('imap.idle yields') if r.instance_of?(Net::IMAP::UntaggedResponse) && r.name == 'EXISTS' imap.idle_done unless r.data == 0 elsif r.instance_of?(Net::IMAP::ContinuationRequest) Clacks.logger.info(r.data.text) end end } end rescue Net::IMAP::BadResponseError => e unless e.message == 'Could not parse command' Clacks.logger.error("#{e.message} (#{e.class})\n#{(e.backtrace || []).join("\n")}") end # reconnect in next loop rescue Net::IMAP::Error, IOError => e # OK: reconnect in next loop rescue Errno::ECONNRESET => e # Connection reset by peer: reconnect in next loop rescue StandardError => e Clacks.logger.error("#{e.message} (#{e.class})\n#{(e.backtrace || []).join("\n")}") sleep(5) unless stopping? end break if stopping? end end
imap_validate_options(options)
click to toggle source
Follows mostly the defaults from the Mail gem
# File lib/clacks/service.rb, line 73 def imap_validate_options(options) options ||= {} options[:mailbox] ||= 'INBOX' options[:count] ||= 5 options[:order] ||= :asc options[:what] ||= :first options[:keys] ||= 'ALL' options[:delete_after_find] ||= false options[:mailbox] = Net::IMAP.encode_utf7(options[:mailbox]) if options[:archivebox] options[:archivebox] = Net::IMAP.encode_utf7(options[:archivebox]) end options end
imap_watchdog()
click to toggle source
tools.ietf.org/rfc/rfc2177.txt
# File lib/clacks/service.rb, line 133 def imap_watchdog Thread.new do loop do begin Clacks.logger.debug('watchdog sleeps') sleep(WATCHDOG_SLEEP) Clacks.logger.debug('watchdog woke up') @imap.idle_done Clacks.logger.debug('watchdog signalled idle process') rescue StandardError => e Clacks.logger.debug { "watchdog received error: #{e.message} (#{e.class})\n#{(e.backtrace || []).join("\n")}" } # noop rescue Exception => e fatal(e) end end end end
poll(processor)
click to toggle source
# File lib/clacks/service.rb, line 193 def poll(processor) polling_msg = if polling? "Clacks polling every #{poll_interval} seconds." else "Clacks polling for messages once." end Clacks.logger.info(polling_msg) find_options = Clacks.config[:find_options] on_mail = Clacks.config[:on_mail] loop do break if stopping? finding { processor.find(find_options) do |mail| if stopping? mail.skip_deletion else begin on_mail.call(mail) rescue StandardError => e Clacks.logger.error(e.message) Clacks.logger.error(e.backtrace) end end end } break if stopping? || !polling? sleep(poll_interval) end end
poll_interval()
click to toggle source
# File lib/clacks/service.rb, line 224 def poll_interval Clacks.config[:poll_interval] end
polling?()
click to toggle source
# File lib/clacks/service.rb, line 228 def polling? poll_interval > 0 end
run_imap()
click to toggle source
# File lib/clacks/service.rb, line 54 def run_imap config = Clacks.config[:imap] options = Clacks.config[:find_options] processor = Mail::IMAP.new(config) if $DEBUG Net::IMAP.debug = true Clacks.logger.level = Logger::DEBUG end imap_validate_options(options) if imap_idle_support?(processor) Clacks.logger.info("Clacks IMAP idling #{config[:user_name]}@#{config[:address]}") imap_idling(processor) else Clacks.logger.info("Clacks IMAP polling #{config[:user_name]}@#{config[:address]}") poll(processor) end end
run_pop3()
click to toggle source
# File lib/clacks/service.rb, line 46 def run_pop3 config = Clacks.config[:pop3] Clacks.logger.info("Clacks POP3 polling #{config[:user_name]}@#{config[:address]}") # TODO: if $DEBUG processor = Mail::IMAP.new(config) poll(processor) end
stopping?()
click to toggle source
# File lib/clacks/service.rb, line 243 def stopping? $STOPPING end