]> git.cworth.org Git - sup/blob - lib/sup/poll.rb
Merge branch 'parser-user-query-fix'
[sup] / lib / sup / poll.rb
1 require 'thread'
2
3 module Redwood
4
5 class PollManager
6   include Singleton
7
8   HookManager.register "before-add-message", <<EOS
9 Executes immediately before a message is added to the index.
10 Variables:
11   message: the new message
12 EOS
13
14   HookManager.register "before-poll", <<EOS
15 Executes immediately before a poll for new messages commences.
16 No variables.
17 EOS
18
19   HookManager.register "after-poll", <<EOS
20 Executes immediately after a poll for new messages completes.
21 Variables:
22                    num: the total number of new messages added in this poll
23              num_inbox: the number of new messages added in this poll which
24                         appear in the inbox (i.e. were not auto-archived).
25 num_inbox_total_unread: the total number of unread messages in the inbox
26          from_and_subj: an array of (from email address, subject) pairs
27    from_and_subj_inbox: an array of (from email address, subject) pairs for
28                         only those messages appearing in the inbox
29 EOS
30
31   DELAY = 300
32
33   def initialize
34     @mutex = Mutex.new
35     @thread = nil
36     @last_poll = nil
37     @polling = false
38     
39     self.class.i_am_the_instance self
40   end
41
42   def buffer
43     b, new = BufferManager.spawn_unless_exists("poll for new messages", :hidden => true, :system => true) { PollMode.new }
44     b
45   end
46
47   def poll
48     return if @polling
49     @polling = true
50     HookManager.run "before-poll"
51
52     BufferManager.flash "Polling for new messages..."
53     num, numi, from_and_subj, from_and_subj_inbox = buffer.mode.poll
54     if num > 0
55       BufferManager.flash "Loaded #{num.pluralize 'new message'}, #{numi} to inbox." 
56     else
57       BufferManager.flash "No new messages." 
58     end
59
60     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] }
61
62     @polling = false
63     [num, numi]
64   end
65
66   def start
67     @thread = Redwood::reporting_thread("periodic poll") do
68       while true
69         sleep DELAY / 2
70         poll if @last_poll.nil? || (Time.now - @last_poll) >= DELAY
71       end
72     end
73   end
74
75   def stop
76     @thread.kill if @thread
77     @thread = nil
78   end
79
80   def do_poll
81     total_num = total_numi = 0
82     from_and_subj = []
83     from_and_subj_inbox = []
84
85     @mutex.synchronize do
86       Index.usual_sources.each do |source|
87 #        yield "source #{source} is done? #{source.done?} (cur_offset #{source.cur_offset} >= #{source.end_offset})"
88         begin
89           yield "Loading from #{source}... " unless source.done? || (source.respond_to?(:has_errors?) && source.has_errors?)
90         rescue SourceError => e
91           Redwood::log "problem getting messages from #{source}: #{e.message}"
92           Redwood::report_broken_sources :force_to_top => true
93           next
94         end
95
96         num = 0
97         numi = 0
98         add_messages_from source do |m, offset, entry|
99           ## always preserve the labels on disk.
100           m.labels = ((m.labels - [:unread, :inbox]) + entry[:label].symbolistize).uniq if entry
101           yield "Found message at #{offset} with labels {#{m.labels * ', '}}"
102           unless entry
103             num += 1
104             from_and_subj << [m.from && m.from.longname, m.subj]
105             if m.has_label?(:inbox) && ([:spam, :deleted, :killed] & m.labels).empty?
106               from_and_subj_inbox << [m.from && m.from.longname, m.subj]
107               numi += 1 
108             end
109           end
110           m
111         end
112         yield "Found #{num} messages, #{numi} to inbox." unless num == 0
113         total_num += num
114         total_numi += numi
115       end
116
117       yield "Done polling; loaded #{total_num} new messages total"
118       @last_poll = Time.now
119       @polling = false
120     end
121     [total_num, total_numi, from_and_subj, from_and_subj_inbox]
122   end
123
124   ## this is the main mechanism for adding new messages to the
125   ## index. it's called both by sup-sync and by PollMode.
126   ##
127   ## for each message in the source, starting from the source's
128   ## starting offset, this methods yields the message, the source
129   ## offset, and the index entry on disk (if any). it expects the
130   ## yield to return the message (possibly altered in some way), and
131   ## then adds it (if new) or updates it (if previously seen).
132   ##
133   ## the labels of the yielded message are the default source
134   ## labels. it is likely that callers will want to replace these with
135   ## the index labels, if they exist, so that state is not lost when
136   ## e.g. a new version of a message from a mailing list comes in.
137   def add_messages_from source, opts={}
138     begin
139       return if source.done? || source.has_errors?
140       
141       source.each do |offset, labels|
142         if source.has_errors?
143           Redwood::log "error loading messages from #{source}: #{source.error.message}"
144           return
145         end
146       
147         labels.each { |l| LabelManager << l }
148         labels = labels + (source.archived? ? [] : [:inbox])
149
150         begin
151           m = Message.new :source => source, :source_info => offset, :labels => labels
152           m.load_from_source!
153
154           if m.source_marked_read?
155             m.remove_label :unread
156             labels.delete :unread
157           end
158
159           docid, entry = Index.load_entry_for_id m.id
160           HookManager.run "before-add-message", :message => m
161           m = yield(m, offset, entry) or next if block_given?
162           times = Index.sync_message m, false, docid, entry, opts
163           UpdateManager.relay self, :added, m unless entry
164         rescue MessageFormatError => e
165           Redwood::log "ignoring erroneous message at #{source}##{offset}: #{e.message}"
166         end
167       end
168     rescue SourceError => e
169       Redwood::log "problem getting messages from #{source}: #{e.message}"
170       Redwood::report_broken_sources :force_to_top => true
171     end
172   end
173 end
174
175 end