Excerpts from William Morgan's message of Thu Dec 11 15:20:08 -0500 2008: > Reformatted excerpts from Florian Bender's message of 2008-12-08: > > after a quick glance at the documentation, I didn't notice any option > > to increase the frequency sup polls a source for new E-Mails. For my > > GMail Account I'll like it, if it'd be polled every minute or so. > > There's no way to do this now, but it would be pretty easy to add.
Christopher Warrington sent a patch to this list in the spring which allows configuration of poll interval on a per-source basis. This should do what's being looked for. I run it locally, mostly so sup doesn't waste time polling static sources (like the mailing list archive from before I joined this list), and it seems to work nicely. Here's the email as I have it saved in my sup-patches/: >From chr...@rice.edu Sat Mar 29 06:32:05 2008 From: chr...@rice.edu (Christopher Warrington) Date: Sat, 29 Mar 2008 05:32:05 -0500 Subject: [sup-talk] [PATCH] polling is now done per source In-Reply-To: <1206784680-sup-3...@chris-tablet> References: <1206784680-sup-3...@chris-tablet> Message-ID: <1206786725-5456-1-git-send-email-chr...@rice.edu> Each source has a poll_interval property. This property is used to determine whether the source should be polled. The user can still for a poll of all sources. --- bin/sup | 4 +- bin/sup-add | 12 ++- lib/sup.rb | 4 +- lib/sup/imap.rb | 6 +- lib/sup/maildir.rb | 6 +- lib/sup/mbox/loader.rb | 4 +- lib/sup/modes/poll-mode.rb | 4 +- lib/sup/poll.rb | 211 +++++++++++++++++++++++++++++++++----------- lib/sup/source.rb | 8 +- 9 files changed, 189 insertions(+), 70 deletions(-) mode change 100755 => 100644 lib/sup/buffer.rb diff --git a/bin/sup b/bin/sup index 84fd77c..86a2d9f 100644 --- a/bin/sup +++ b/bin/sup @@ -197,7 +197,7 @@ begin end end unless $opts[:no_initial_poll] - imode.load_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread("poll after loading inbox") { sleep 1; PollManager.poll } unless $opts[:no_threads] || $opts[:no_initial_poll] } + imode.load_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread("poll after loading inbox") { sleep 1; PollManager.auto_poll } unless $opts[:no_threads] || $opts[:no_initial_poll] } if $opts[:compose] ComposeMode.spawn_nicely :to_default => $opts[:compose] @@ -263,7 +263,7 @@ begin when :compose ComposeMode.spawn_nicely when :poll - reporting_thread("user-invoked poll") { PollManager.poll } + reporting_thread("user-invoked poll") { PollManager.forced_poll } when :recall_draft case Index.num_results_for :label => :draft when 0 diff --git a/bin/sup-add b/bin/sup-add index 50bbb29..88705e1 100644 --- a/bin/sup-add +++ b/bin/sup-add @@ -39,6 +39,7 @@ EOS opt :unusual, "Do not automatically poll these sources for new messages." opt :labels, "A comma-separated set of labels to apply to all messages from this source", :type => String opt :force_new, "Create a new account for this source, even if one already exists." + opt :poll_interval, "The interval (in seconds) between new message polls. The default is #{Redwood::DEFAULT_POLL_INTERVAL}.", :type => :int end Trollop::die "require one or more sources" if ARGV.empty? @@ -84,6 +85,9 @@ index.lock_or_die begin index.load_sources + Trollop::die "The poll interval must be a positive integer." if $opts[:poll_interval] <= 0 + poll_interval = $opts[:poll_interval] || Redwood::DEFAULT_POLL_INTERVAL + ARGV.each do |uri| labels = $opts[:labels] ? $opts[:labels].split(/\s*,\s*/).uniq : [] @@ -100,14 +104,14 @@ begin say "For SSH connections, if you will use public key authentication, you may leave the username and password blank." say "" username, password = get_login_info uri, index.sources - Redwood::MBox::SSHLoader.new uri, username, password, nil, !$opts[:unusual], $opts[:archive], nil, labels + Redwood::MBox::SSHLoader.new uri, username, password, nil, !$opts[:unusual], $opts[:archive], nil, poll_interval, labels when "imap", "imaps" username, password = get_login_info uri, index.sources - Redwood::IMAP.new uri, username, password, nil, !$opts[:unusual], $opts[:archive], nil, labels + Redwood::IMAP.new uri, username, password, nil, !$opts[:unusual], $opts[:archive], nil, poll_interval, labels when "maildir" - Redwood::Maildir.new uri, nil, !$opts[:unusual], $opts[:archive], nil, labels + Redwood::Maildir.new uri, nil, !$opts[:unusual], $opts[:archive], nil, poll_interval, labels when "mbox" - Redwood::MBox::Loader.new uri, nil, !$opts[:unusual], $opts[:archive], nil, labels + Redwood::MBox::Loader.new uri, nil, !$opts[:unusual], $opts[:archive], nil, poll_interval, labels when nil Trollop::die "Sources must be specified with an URI" else diff --git a/lib/sup.rb b/lib/sup.rb index 1946f3c..c27a4bc 100644 --- a/lib/sup.rb +++ b/lib/sup.rb @@ -50,6 +50,8 @@ module Redwood YAML_DOMAIN = "masanjin.net" YAML_DATE = "2006-10-01" + DEFAULT_POLL_INTERVAL = 300 + ## record exceptions thrown in threads nicely def reporting_thread name if $opts[:no_threads] @@ -72,7 +74,7 @@ module Redwood def save_yaml_obj object, fn, safe=false if safe safe_fn = "#{File.dirname fn}/safe_#{File.basename fn}" - mode = File.stat(fn) if File.exists? fn + mode = File.stat(fn).mode if File.exists? fn File.open(safe_fn, "w", mode) { |f| f.puts object.to_yaml } FileUtils.mv safe_fn, fn else diff --git a/lib/sup/buffer.rb b/lib/sup/buffer.rb old mode 100755 new mode 100644 diff --git a/lib/sup/imap.rb b/lib/sup/imap.rb index 1d36976..8b58cba 100644 --- a/lib/sup/imap.rb +++ b/lib/sup/imap.rb @@ -51,13 +51,13 @@ class IMAP < Source attr_accessor :username, :password yaml_properties :uri, :username, :password, :cur_offset, :usual, - :archived, :id, :labels + :archived, :id, :poll_interval, :labels - def initialize uri, username, password, last_idate=nil, usual=true, archived=false, id=nil, labels=[] + def initialize uri, username, password, last_idate=nil, usual=true, archived=false, id=nil, poll_interval=nil, labels=[] raise ArgumentError, "username and password must be specified" unless username && password raise ArgumentError, "not an imap uri" unless uri =~ %r!imaps?://! - super uri, last_idate, usual, archived, id + super uri, last_idate, usual, archived, id, [poll_interval, SCAN_INTERVAL].max @parsed_uri = URI(uri) @username = username diff --git a/lib/sup/maildir.rb b/lib/sup/maildir.rb index 584e657..ef71e0b 100644 --- a/lib/sup/maildir.rb +++ b/lib/sup/maildir.rb @@ -12,9 +12,9 @@ class Maildir < Source SCAN_INTERVAL = 30 # seconds ## remind me never to use inheritance again. - yaml_properties :uri, :cur_offset, :usual, :archived, :id, :labels - def initialize uri, last_date=nil, usual=true, archived=false, id=nil, labels=[] - super uri, last_date, usual, archived, id + yaml_properties :uri, :cur_offset, :usual, :archived, :id, :poll_interval, :labels + def initialize uri, last_date=nil, usual=true, archived=false, id=nil, poll_interval=nil, labels=[] + super uri, last_date, usual, archived, id, [poll_interval, SCAN_INTERVAL].max uri = URI(Source.expand_filesystem_uri(uri)) raise ArgumentError, "not a maildir URI" unless uri.scheme == "maildir" diff --git a/lib/sup/mbox/loader.rb b/lib/sup/mbox/loader.rb index 7fe9129..44317d5 100644 --- a/lib/sup/mbox/loader.rb +++ b/lib/sup/mbox/loader.rb @@ -9,7 +9,7 @@ class Loader < Source attr_accessor :labels ## uri_or_fp is horrific. need to refactor. - def initialize uri_or_fp, start_offset=nil, usual=true, archived=false, id=nil, labels=[] + def initialize uri_or_fp, start_offset=nil, usual=true, archived=false, id=nil, poll_interval=nil, labels=[] @mutex = Mutex.new @labels = ((labels || []) - LabelManager::RESERVED_LABELS).uniq.freeze @@ -26,7 +26,7 @@ class Loader < Source @path = uri_or_fp.path end - super uri_or_fp, start_offset, usual, archived, id + super uri_or_fp, start_offset, usual, archived, id, poll_interval end def file_path; @path end diff --git a/lib/sup/modes/poll-mode.rb b/lib/sup/modes/poll-mode.rb index 5849f3e..5521cdc 100644 --- a/lib/sup/modes/poll-mode.rb +++ b/lib/sup/modes/poll-mode.rb @@ -10,11 +10,11 @@ class PollMode < LogMode self << s + "\n" end - def poll + def poll sources puts unless @new @new = false puts "Poll started at #{Time.now}" - PollManager.do_poll { |s| puts s } + PollManager.do_poll_sources(sources) { |s| puts s } end end diff --git a/lib/sup/poll.rb b/lib/sup/poll.rb index d32c893..1ff8014 100644 --- a/lib/sup/poll.rb +++ b/lib/sup/poll.rb @@ -12,13 +12,22 @@ Variables: EOS HookManager.register "before-poll", <<EOS +Executes immediately before any poll for new messages commences. +Variables: + accts: an array of source URIs that will be polled +EOS + + HookManager.register "before-acct-poll", <<EOS Executes immediately before a poll for new messages commences. -No variables. +Variables: + acct: the URI of the account being polled EOS - HookManager.register "after-poll", <<EOS -Executes immediately after a poll for new messages completes. + HookManager.register "after-acct-poll", <<EOS +Executes immediately after a poll for new messages completes. The statistics are +only for the account specified. Variables: + acct: the URI of the account being polled num: the total number of new messages added in this poll num_inbox: the number of new messages added in this poll which appear in the inbox (i.e. were not auto-archived). @@ -28,14 +37,25 @@ num_inbox_total_unread: the total number of unread messages in the inbox only those messages appearing in the inbox EOS - DELAY = 300 + HookManager.register "after-poll", <<EOS +Executes immediately after any poll for new messages completes. The statistics +are for all sources polled. +Variables: + accts: an array of source URIs that were polled + num: the total number of new messages added in this poll + num_inbox: the number of new messages added in this poll which + appear in the inbox (i.e. were not auto-archived). +num_inbox_total_unread: the total number of unread messages in the inbox + from_and_subj: an array of (from email address, subject) pairs + from_and_subj_inbox: an array of (from email address, subject) pairs for + only those messages appearing in the inbox +EOS def initialize @mutex = Mutex.new @thread = nil - @last_poll = nil @polling = false - + self.class.i_am_the_instance self end @@ -44,47 +64,113 @@ EOS b end - def poll + def start + ## Periodically calls auto_poll, effectively automatically polling + ## in the background. + ## + ## auto_poll is called more frequently than the smallest poll + ## interval. auto_poll is not guaranteed to be called in multiples + ## of poll intervals. I.e., don't rely on auto_poll to be called + ## every x seconds: when auto_poll is called assume an + ## indeterminate amount of time has passed. + @min_poll_interval = Index.usual_sources.collect{|s| s.poll_interval}.select{|pi| !pi.nil?}.min || Redwood::DEFAULT_POLL_INTERVAL + + Redwood::log "Background poll interval is #...@min_poll_interval} seconds." + + @thread = Redwood::reporting_thread("periodic poll") do + while true + Redwood::log "Sleeping for #...@min_poll_interval / 2} seconds." + + sleep @min_poll_interval / 2 + auto_poll + end + end + end + + def stop + @thread.kill if @thread + @thread = nil + end + + def auto_poll + ## This method is called by the thread spawned in start. It + ## collects the sources that should be polled + ## (source.poll_interval has expired or the source has never been + ## polled) and polls them. + ## + ## Returns an array [# of new messages, # of new messages to + ## inbox, new message subject/name array pairs, subject/name array + ## pairs loaded to inbox] + + sources_to_poll = [] + @mutex.synchronize do # William, do we need to synchronize here? + begin + sources_to_poll = Index.usual_sources.select do |source| + begin + source.last_poll.nil? || (Time.now - source.last_poll) >= source.poll_interval + end + end + end + end + + poll_sources sources_to_poll + end + + def forced_poll + ## This method is called when the user explicitly requests a poll. + ## + ## Returns an array [# of new messages, # of new messages to + ## inbox, new message subject/name array pairs, subject/name array + ## pairs loaded to inbox] + poll_sources Index.usual_sources + end + + def poll_sources sources + ## Polls the given sources. Clients of PollManager should call + ## this method, not do_poll_sources or poll_source. (Well, only + ## PollMode should call do_poll_sources) + ## + ## Returns an array [# of new messages, # of new messages to + ## inbox, new message subject/name array pairs, subject/name array + ## pairs loaded to inbox] return if @polling @polling = true - HookManager.run "before-poll" + + source_uris = sources.map{|s| s.uri} + + HookManager.run "before-acct-poll", :accts => source_uris BufferManager.flash "Polling for new messages..." - num, numi, from_and_subj, from_and_subj_inbox = buffer.mode.poll + num, numi, from_subj, from_subj_inbox = buffer.mode.poll sources if num > 0 BufferManager.flash "Loaded #{num.pluralize 'new message'}, #{numi} to inbox." else BufferManager.flash "No new messages." end - HookManager.run "after-poll", :num => num, :num_inbox => numi, :from_and_subj => from_and_subj, :from_and_subj_inbox => from_and_subj_inbox, :num_inbox_total_unread => lambda { Index.num_results_for :labels => [:inbox, :unread] } + HookManager.run "after-poll", :accts => source_uris, :num => num, :num_inbox => numi, :from_and_subj => from_subj, :from_and_subj_inbox => from_subj_inbox, :num_inbox_total_unread => lambda { Index.num_results_for :labels => [:inbox, :unread] } @polling = false - [num, numi] - end - - def start - @thread = Redwood::reporting_thread("periodic poll") do - while true - sleep DELAY / 2 - poll if @last_poll.nil? || (Time.now - @last_poll) >= DELAY - end - end + [num, numi, from_subj, from_subj_inbox] end - def stop - @thread.kill if @thread - @thread = nil - end + def do_poll_sources sources, &block + ## Polls each source, keeping track of vital statistics (number + ## loaded, name/e-mail pairs, &c.) about the poll results. + ## + ## We need explicit access to the block so that we can pass it to + ## poll_source. + ## + ## Returns an array [total # of new messages, total # of new + ## messages to inbox, all new message subject/name array pairs, + ## all subject/name array pairs loaded to inbox] - def do_poll total_num = total_numi = 0 - from_and_subj = [] - from_and_subj_inbox = [] + total_from_and_subj = [] + total_from_and_subj_inbox = [] @mutex.synchronize do - Index.usual_sources.each do |source| -# yield "source #{source} is done? #{source.done?} (cur_offset #{source.cur_offset} >= #{source.end_offset})" + sources.each do |source| begin yield "Loading from #{source}... " unless source.done? || source.has_errors? rescue SourceError => e @@ -93,32 +179,57 @@ EOS next end - num = 0 - numi = 0 - add_messages_from source do |m, offset, entry| - ## always preserve the labels on disk. - m.labels = entry[:label].split(/\s+/).map { |x| x.intern } if entry - yield "Found message at #{offset} with labels {#{m.labels * ', '}}" - unless entry - num += 1 - from_and_subj << [m.from.longname, m.subj] - if m.has_label?(:inbox) && ([:spam, :deleted, :killed] & m.labels).empty? - from_and_subj_inbox << [m.from.longname, m.subj] - numi += 1 - end - end - m - end - yield "Found #{num} messages, #{numi} to inbox." unless num == 0 + HookManager.run "before-acct-poll", :acct => source.uri + + BufferManager.flash "Polling #{source.uri} for new messages..." + num, numi, from_and_subj, from_and_subj_inbox = poll_source source, &block + total_num += num total_numi += numi + total_from_and_subj += from_and_subj + total_from_and_subj_inbox += from_and_subj_inbox + + HookManager.run "after-acct-poll", :acct => source.uri, :num => num, :num_inbox => numi, :from_and_subj => from_and_subj, :from_and_subj_inbox => from_and_subj_inbox, :num_inbox_total_unread => lambda { Index.num_results_for :labels => [:inbox, :unread] } end + end + + [total_num, total_numi, total_from_and_subj, total_from_and_subj_inbox] + end - yield "Done polling; loaded #{total_num} new messages total" - @last_poll = Time.now - @polling = false + def poll_source source + ## Polls the given source for new messages. + ## + ## @mutex must be held before calling! See do_poll_sources. + ## + ## Returns an array [# of new messages, # of new messages to + ## inbox, new message subject/name array pairs, subject/name array + ## pairs loaded to inbox] + + num = 0 + numi = 0 + from_and_subj = [] + from_and_subj_inbox = [] + + add_messages_from source do |m, offset, entry| + ## always preserve the labels on disk. + m.labels = entry[:label].split(/\s+/).map { |x| x.intern } if entry + yield "Found message at #{offset} with labels {#{m.labels * ', '}}" + unless entry + num += 1 + from_and_subj << [m.from.longname, m.subj] + if m.has_label?(:inbox) && ([:spam, :deleted, :killed] & m.labels).empty? + from_and_subj_inbox << [m.from.longname, m.subj] + numi += 1 + end + end + m end - [total_num, total_numi, from_and_subj, from_and_subj_inbox] + + yield "For source #{source.uri}, found #{num} messages, #{numi} to inbox." unless num == 0 + + source.last_poll = Time.now + + [num, numi, from_and_subj, from_and_subj_inbox] end ## this is the main mechanism for adding new messages to the diff --git a/lib/sup/source.rb b/lib/sup/source.rb index 6510aae..fd8d381 100644 --- a/lib/sup/source.rb +++ b/lib/sup/source.rb @@ -62,10 +62,10 @@ class Source ## dirty? means cur_offset has changed, so the source info needs to ## be re-saved to sources.yaml. bool_reader :usual, :archived, :dirty - attr_reader :uri, :cur_offset - attr_accessor :id + attr_reader :uri, :cur_offset, :poll_interval + attr_accessor :id, :last_poll - def initialize uri, initial_offset=nil, usual=true, archived=false, id=nil + def initialize uri, initial_offset=nil, usual=true, archived=false, id=nil, poll_interval=nil raise ArgumentError, "id must be an integer: #{id.inspect}" unless id.is_a? Fixnum if id @uri = uri @@ -73,6 +73,8 @@ class Source @usual = usual @archived = archived @id = id + @poll_interval = poll_interval || Redwood::DEFAULT_POLL_INTERVAL #seconds + @last_poll = nil @dirty = false end -- 1.5.4
signature.asc
Description: PGP signature
_______________________________________________ sup-talk mailing list sup-talk@rubyforge.org http://rubyforge.org/mailman/listinfo/sup-talk