sup 0.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sup might be problematic. Click here for more details.
- data/History.txt +10 -0
- data/bin/sup +50 -68
- data/doc/NewUserGuide.txt +11 -7
- data/doc/TODO +34 -22
- data/lib/sup.rb +30 -24
- data/lib/sup/buffer.rb +124 -39
- data/lib/sup/colormap.rb +4 -4
- data/lib/sup/draft.rb +1 -1
- data/lib/sup/hook.rb +18 -5
- data/lib/sup/imap.rb +11 -13
- data/lib/sup/index.rb +52 -14
- data/lib/sup/keymap.rb +1 -1
- data/lib/sup/logger.rb +1 -0
- data/lib/sup/maildir.rb +9 -0
- data/lib/sup/mbox.rb +3 -1
- data/lib/sup/message-chunks.rb +21 -7
- data/lib/sup/message.rb +31 -15
- data/lib/sup/mode.rb +2 -0
- data/lib/sup/modes/buffer-list-mode.rb +7 -3
- data/lib/sup/modes/compose-mode.rb +14 -16
- data/lib/sup/modes/contact-list-mode.rb +2 -2
- data/lib/sup/modes/edit-message-mode.rb +55 -23
- data/lib/sup/modes/forward-mode.rb +22 -5
- data/lib/sup/modes/inbox-mode.rb +3 -7
- data/lib/sup/modes/label-list-mode.rb +30 -10
- data/lib/sup/modes/label-search-results-mode.rb +12 -0
- data/lib/sup/modes/line-cursor-mode.rb +13 -0
- data/lib/sup/modes/log-mode.rb +0 -6
- data/lib/sup/modes/poll-mode.rb +0 -3
- data/lib/sup/modes/reply-mode.rb +19 -11
- data/lib/sup/modes/scroll-mode.rb +111 -20
- data/lib/sup/modes/search-results-mode.rb +21 -0
- data/lib/sup/modes/text-mode.rb +10 -2
- data/lib/sup/modes/thread-index-mode.rb +200 -90
- data/lib/sup/modes/thread-view-mode.rb +27 -10
- data/lib/sup/person.rb +1 -0
- data/lib/sup/poll.rb +15 -7
- data/lib/sup/source.rb +6 -1
- data/lib/sup/suicide.rb +1 -1
- data/lib/sup/textfield.rb +14 -14
- data/lib/sup/thread.rb +6 -2
- data/lib/sup/util.rb +111 -9
- metadata +13 -6
data/lib/sup/buffer.rb
CHANGED
@@ -74,9 +74,13 @@ class Buffer
|
|
74
74
|
mode.resize rows, cols
|
75
75
|
end
|
76
76
|
|
77
|
-
def redraw
|
78
|
-
|
79
|
-
|
77
|
+
def redraw status
|
78
|
+
if @dirty
|
79
|
+
draw status
|
80
|
+
else
|
81
|
+
draw_status status
|
82
|
+
end
|
83
|
+
|
80
84
|
commit
|
81
85
|
end
|
82
86
|
|
@@ -87,9 +91,9 @@ class Buffer
|
|
87
91
|
@w.noutrefresh
|
88
92
|
end
|
89
93
|
|
90
|
-
def draw
|
94
|
+
def draw status
|
91
95
|
@mode.draw
|
92
|
-
draw_status
|
96
|
+
draw_status status
|
93
97
|
commit
|
94
98
|
end
|
95
99
|
|
@@ -110,9 +114,8 @@ class Buffer
|
|
110
114
|
@w.clear
|
111
115
|
end
|
112
116
|
|
113
|
-
def draw_status
|
114
|
-
write @height - 1, 0,
|
115
|
-
:color => :status_color
|
117
|
+
def draw_status status
|
118
|
+
write @height - 1, 0, status, :color => :status_color
|
116
119
|
end
|
117
120
|
|
118
121
|
def focus
|
@@ -133,6 +136,35 @@ class BufferManager
|
|
133
136
|
|
134
137
|
attr_reader :focus_buf
|
135
138
|
|
139
|
+
## we have to define the key used to continue in-buffer search here, because
|
140
|
+
## it has special semantics that BufferManager deals with---current searches
|
141
|
+
## are canceled by any keypress except this one.
|
142
|
+
CONTINUE_IN_BUFFER_SEARCH_KEY = "n"
|
143
|
+
|
144
|
+
HookManager.register "status-bar-text", <<EOS
|
145
|
+
Sets the status bar. The default status bar contains the mode name, the buffer
|
146
|
+
title, and the mode status. Note that this will be called at least once per
|
147
|
+
keystroke, so excessive computation is discouraged.
|
148
|
+
|
149
|
+
Variables:
|
150
|
+
num_inbox: number of messages in inbox
|
151
|
+
num_inbox_unread: total number of messages marked as unread
|
152
|
+
num_total: total number of messages in the index
|
153
|
+
num_spam: total number of messages marked as spam
|
154
|
+
title: title of the current buffer
|
155
|
+
mode: current mode name (string)
|
156
|
+
status: current mode status (string)
|
157
|
+
Return value: a string to be used as the status bar.
|
158
|
+
EOS
|
159
|
+
|
160
|
+
HookManager.register "terminal-title-text", <<EOS
|
161
|
+
Sets the title of the current terminal, if applicable. Note that this will be
|
162
|
+
called at least once per keystroke, so excessive computation is discouraged.
|
163
|
+
|
164
|
+
Variables: the same as status-bar-text hook.
|
165
|
+
Return value: a string to be used as the terminal title.
|
166
|
+
EOS
|
167
|
+
|
136
168
|
def initialize
|
137
169
|
@name_map = {}
|
138
170
|
@buffers = []
|
@@ -150,7 +182,8 @@ class BufferManager
|
|
150
182
|
def buffers; @name_map.to_a; end
|
151
183
|
|
152
184
|
def focus_on buf
|
153
|
-
|
185
|
+
return unless @buffers.member? buf
|
186
|
+
|
154
187
|
return if buf == @focus_buf
|
155
188
|
@focus_buf.blur if @focus_buf
|
156
189
|
@focus_buf = buf
|
@@ -158,7 +191,7 @@ class BufferManager
|
|
158
191
|
end
|
159
192
|
|
160
193
|
def raise_to_front buf
|
161
|
-
|
194
|
+
return unless @buffers.member? buf
|
162
195
|
|
163
196
|
@buffers.delete buf
|
164
197
|
if @buffers.length > 0 && @buffers.last.force_to_top?
|
@@ -190,7 +223,13 @@ class BufferManager
|
|
190
223
|
end
|
191
224
|
|
192
225
|
def handle_input c
|
193
|
-
|
226
|
+
if @focus_buf
|
227
|
+
if @focus_buf.mode.in_search? && c != CONTINUE_IN_BUFFER_SEARCH_KEY[0]
|
228
|
+
@focus_buf.mode.cancel_search!
|
229
|
+
@focus_buf.mark_dirty
|
230
|
+
end
|
231
|
+
@focus_buf.mode.handle_input c
|
232
|
+
end
|
194
233
|
end
|
195
234
|
|
196
235
|
def exists? n; @name_map.member? n; end
|
@@ -204,16 +243,27 @@ class BufferManager
|
|
204
243
|
def completely_redraw_screen
|
205
244
|
return if @shelled
|
206
245
|
|
246
|
+
status, title = get_status_and_title(@focus_buf) # must be called outside of the ncurses lock
|
247
|
+
|
207
248
|
Ncurses.sync do
|
208
249
|
@dirty = true
|
209
250
|
Ncurses.clear
|
210
|
-
draw_screen :sync => false
|
251
|
+
draw_screen :sync => false, :status => status, :title => title
|
211
252
|
end
|
212
253
|
end
|
213
254
|
|
214
255
|
def draw_screen opts={}
|
215
256
|
return if @shelled
|
216
257
|
|
258
|
+
status, title =
|
259
|
+
if opts.member? :status
|
260
|
+
[opts[:status], opts[:title]]
|
261
|
+
else
|
262
|
+
get_status_and_title(@focus_buf) # must be called outside of the ncurses lock
|
263
|
+
end
|
264
|
+
|
265
|
+
print "\033]2;#{title}\07" if title
|
266
|
+
|
217
267
|
Ncurses.mutex.lock unless opts[:sync] == false
|
218
268
|
|
219
269
|
## disabling this for the time being, to help with debugging
|
@@ -222,7 +272,7 @@ class BufferManager
|
|
222
272
|
false && @buffers.inject(@dirty) do |dirty, buf|
|
223
273
|
buf.resize Ncurses.rows - minibuf_lines, Ncurses.cols
|
224
274
|
#dirty ? buf.draw : buf.redraw
|
225
|
-
buf.draw
|
275
|
+
buf.draw status
|
226
276
|
dirty
|
227
277
|
end
|
228
278
|
|
@@ -230,7 +280,7 @@ class BufferManager
|
|
230
280
|
if true
|
231
281
|
buf = @buffers.last
|
232
282
|
buf.resize Ncurses.rows - minibuf_lines, Ncurses.cols
|
233
|
-
@dirty ? buf.draw : buf.redraw
|
283
|
+
@dirty ? buf.draw(status) : buf.redraw(status)
|
234
284
|
end
|
235
285
|
|
236
286
|
draw_minibuf :sync => false unless opts[:skip_minibuf]
|
@@ -241,17 +291,21 @@ class BufferManager
|
|
241
291
|
Ncurses.mutex.unlock unless opts[:sync] == false
|
242
292
|
end
|
243
293
|
|
244
|
-
##
|
245
|
-
##
|
246
|
-
##
|
294
|
+
## if the named buffer already exists, pops it to the front without
|
295
|
+
## calling the block. otherwise, gets the mode from the block and
|
296
|
+
## creates a new buffer. returns two things: the buffer, and a boolean
|
297
|
+
## indicating whether it's a new buffer or not.
|
247
298
|
def spawn_unless_exists title, opts={}
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
299
|
+
new =
|
300
|
+
if @name_map.member? title
|
301
|
+
raise_to_front @name_map[title] unless opts[:hidden]
|
302
|
+
false
|
303
|
+
else
|
304
|
+
mode = yield
|
305
|
+
spawn title, mode, opts
|
306
|
+
true
|
307
|
+
end
|
308
|
+
[@name_map[title], new]
|
255
309
|
end
|
256
310
|
|
257
311
|
def spawn title, mode, opts={}
|
@@ -334,7 +388,9 @@ class BufferManager
|
|
334
388
|
## TODO: something intelligent here
|
335
389
|
## for now I will simply prohibit killing the inbox buffer.
|
336
390
|
else
|
337
|
-
|
391
|
+
last = @buffers.last
|
392
|
+
@focus_buf ||= last
|
393
|
+
raise_to_front last
|
338
394
|
end
|
339
395
|
end
|
340
396
|
|
@@ -344,15 +400,13 @@ class BufferManager
|
|
344
400
|
end
|
345
401
|
end
|
346
402
|
|
347
|
-
def ask_many_with_completions domain, question, completions, default=nil
|
403
|
+
def ask_many_with_completions domain, question, completions, default=nil
|
348
404
|
ask domain, question, default do |partial|
|
349
405
|
prefix, target =
|
350
|
-
case partial
|
406
|
+
case partial
|
351
407
|
when /^\s*$/
|
352
408
|
["", ""]
|
353
|
-
when /^(
|
354
|
-
[$1, ""]
|
355
|
-
when /^(.*#{sep})?(.+?)$/
|
409
|
+
when /^(.*\s+)?(.*?)$/
|
356
410
|
[$1 || "", $2]
|
357
411
|
else
|
358
412
|
raise "william screwed up completion: #{partial.inspect}"
|
@@ -362,6 +416,17 @@ class BufferManager
|
|
362
416
|
end
|
363
417
|
end
|
364
418
|
|
419
|
+
def ask_many_emails_with_completions domain, question, completions, default=nil
|
420
|
+
ask domain, question, default do |partial|
|
421
|
+
prefix, target = partial.split_on_commas_with_remainder
|
422
|
+
Redwood::log "before: prefix #{prefix.inspect}, target #{target.inspect}"
|
423
|
+
target ||= prefix.pop || ""
|
424
|
+
prefix = prefix.join(", ") + (prefix.empty? ? "" : ", ")
|
425
|
+
Redwood::log "after: prefix #{prefix.inspect}, target #{target.inspect}"
|
426
|
+
completions.select { |x| x =~ /^#{target}/i }.map { |x| [prefix + x, x] }
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
365
430
|
def ask_for_filename domain, question, default=nil
|
366
431
|
answer = ask domain, question, default do |s|
|
367
432
|
if s =~ /(~([^\s\/]*))/ # twiddle directory expansion
|
@@ -423,13 +488,11 @@ class BufferManager
|
|
423
488
|
default = default_contacts.map { |s| s.to_s }.join(" ")
|
424
489
|
default += " " unless default.empty?
|
425
490
|
|
426
|
-
recent = Index.load_contacts(AccountManager.user_emails, :num => 10).map { |c| [c.
|
427
|
-
contacts = ContactManager.contacts.map { |c| [ContactManager.alias_for(c), c.
|
428
|
-
|
429
|
-
Redwood::log "recent: #{recent.inspect}"
|
491
|
+
recent = Index.load_contacts(AccountManager.user_emails, :num => 10).map { |c| [c.full_address, c.email] }
|
492
|
+
contacts = ContactManager.contacts.map { |c| [ContactManager.alias_for(c), c.full_address, c.email] }
|
430
493
|
|
431
494
|
completions = (recent + contacts).flatten.uniq.sort
|
432
|
-
answer = BufferManager.
|
495
|
+
answer = BufferManager.ask_many_emails_with_completions domain, question, completions, default
|
433
496
|
|
434
497
|
if answer
|
435
498
|
answer.split_on_commas.map { |x| ContactManager.contact_for(x.downcase) || PersonManager.person_for(x) }
|
@@ -454,12 +517,10 @@ class BufferManager
|
|
454
517
|
tf.activate question, default, &block
|
455
518
|
@dirty = true
|
456
519
|
draw_screen :skip_minibuf => true, :sync => false
|
520
|
+
tf.position_cursor
|
521
|
+
Ncurses.refresh
|
457
522
|
end
|
458
523
|
|
459
|
-
ret = nil
|
460
|
-
tf.position_cursor
|
461
|
-
Ncurses.sync { Ncurses.refresh }
|
462
|
-
|
463
524
|
while true
|
464
525
|
c = Ncurses.nonblocking_getch
|
465
526
|
next unless c # getch timeout
|
@@ -627,6 +688,30 @@ class BufferManager
|
|
627
688
|
end
|
628
689
|
|
629
690
|
private
|
691
|
+
def default_status_bar buf
|
692
|
+
" [#{buf.mode.name}] #{buf.title} #{buf.mode.status}"
|
693
|
+
end
|
694
|
+
|
695
|
+
def default_terminal_title buf
|
696
|
+
"Sup #{Redwood::VERSION} :: #{buf.title}"
|
697
|
+
end
|
698
|
+
|
699
|
+
def get_status_and_title buf
|
700
|
+
opts = {
|
701
|
+
:num_inbox => lambda { Index.num_results_for :label => :inbox },
|
702
|
+
:num_inbox_unread => lambda { Index.num_results_for :labels => [:inbox, :unread] },
|
703
|
+
:num_total => lambda { Index.size },
|
704
|
+
:num_spam => lambda { Index.num_results_for :label => :spam },
|
705
|
+
:title => buf.title,
|
706
|
+
:mode => buf.mode.name,
|
707
|
+
:status => buf.mode.status
|
708
|
+
}
|
709
|
+
|
710
|
+
statusbar_text = HookManager.run("status-bar-text", opts) || default_status_bar(buf)
|
711
|
+
term_title_text = HookManager.run("terminal-title-text", opts) || default_terminal_title(buf)
|
712
|
+
|
713
|
+
[statusbar_text, term_title_text]
|
714
|
+
end
|
630
715
|
|
631
716
|
def users
|
632
717
|
unless @users
|
data/lib/sup/colormap.rb
CHANGED
@@ -22,14 +22,14 @@ class Colormap
|
|
22
22
|
[]) + [nil]
|
23
23
|
end
|
24
24
|
|
25
|
-
def add sym, fg, bg,
|
26
|
-
raise ArgumentError, "color for #{sym} already defined" if
|
27
|
-
@entries.member? sym
|
25
|
+
def add sym, fg, bg, attr=nil, opts={}
|
26
|
+
raise ArgumentError, "color for #{sym} already defined" if @entries.member? sym
|
28
27
|
raise ArgumentError, "color '#{fg}' unknown" unless CURSES_COLORS.include? fg
|
29
28
|
raise ArgumentError, "color '#{bg}' unknown" unless CURSES_COLORS.include? bg
|
29
|
+
attrs = [attr].flatten.compact
|
30
30
|
|
31
31
|
@entries[sym] = [fg, bg, attrs, nil]
|
32
|
-
@entries[highlight_sym(sym)] = highlight_for(fg, bg, attrs) + [nil]
|
32
|
+
@entries[highlight_sym(sym)] = opts[:highlight] ? @entries[opts[:highlight]] : highlight_for(fg, bg, attrs) + [nil]
|
33
33
|
end
|
34
34
|
|
35
35
|
def highlight_sym sym
|
data/lib/sup/draft.rb
CHANGED
@@ -47,7 +47,7 @@ class DraftLoader < Source
|
|
47
47
|
def initialize cur_offset=0
|
48
48
|
dir = Redwood::DRAFT_DIR
|
49
49
|
Dir.mkdir dir unless File.exists? dir
|
50
|
-
super
|
50
|
+
super DraftManager.source_name, cur_offset, true, false
|
51
51
|
@dir = dir
|
52
52
|
end
|
53
53
|
|
data/lib/sup/hook.rb
CHANGED
@@ -9,9 +9,10 @@ class HookManager
|
|
9
9
|
##
|
10
10
|
## i don't bother providing setters, since i'm pretty sure the
|
11
11
|
## charade will fall apart pretty quickly with respect to scoping.
|
12
|
-
##
|
12
|
+
## "fail-fast", we'll call it.
|
13
13
|
class HookContext
|
14
14
|
def initialize name
|
15
|
+
@__say_id = nil
|
15
16
|
@__name = name
|
16
17
|
@__locals = {}
|
17
18
|
end
|
@@ -21,7 +22,7 @@ class HookManager
|
|
21
22
|
def method_missing m, *a
|
22
23
|
case @__locals[m]
|
23
24
|
when Proc
|
24
|
-
@__locals[m].call(*a)
|
25
|
+
@__locals[m] = @__locals[m].call(*a) # only call the proc once
|
25
26
|
when nil
|
26
27
|
super
|
27
28
|
else
|
@@ -30,8 +31,12 @@ class HookManager
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def say s
|
33
|
-
|
34
|
-
|
34
|
+
if BufferManager.instantiated?
|
35
|
+
@__say_id = BufferManager.say s, @__say_id
|
36
|
+
BufferManager.draw_screen
|
37
|
+
else
|
38
|
+
log s
|
39
|
+
end
|
35
40
|
end
|
36
41
|
|
37
42
|
def log s
|
@@ -39,7 +44,12 @@ class HookManager
|
|
39
44
|
end
|
40
45
|
|
41
46
|
def ask_yes_or_no q
|
42
|
-
BufferManager.
|
47
|
+
if BufferManager.instantiated?
|
48
|
+
BufferManager.ask_yes_or_no q
|
49
|
+
else
|
50
|
+
print q
|
51
|
+
gets.chomp.downcase == 'y'
|
52
|
+
end
|
43
53
|
end
|
44
54
|
|
45
55
|
def __binding
|
@@ -75,6 +85,7 @@ class HookManager
|
|
75
85
|
rescue Exception => e
|
76
86
|
log "error running hook: #{e.message}"
|
77
87
|
log e.backtrace.join("\n")
|
88
|
+
@hooks[name] = nil # disable it
|
78
89
|
BufferManager.flash "Error running hook: #{e.message}"
|
79
90
|
end
|
80
91
|
context.__cleanup
|
@@ -101,6 +112,8 @@ EOS
|
|
101
112
|
end
|
102
113
|
end
|
103
114
|
|
115
|
+
def enabled? name; !hook_for(name).nil? end
|
116
|
+
|
104
117
|
private
|
105
118
|
|
106
119
|
def hook_for name
|
data/lib/sup/imap.rb
CHANGED
@@ -87,17 +87,8 @@ class IMAP < Source
|
|
87
87
|
end
|
88
88
|
def ssl?; @parsed_uri.scheme == 'imaps' end
|
89
89
|
|
90
|
-
def check
|
91
|
-
|
92
|
-
|
93
|
-
ids =
|
94
|
-
@mutex.synchronize do
|
95
|
-
unsynchronized_scan_mailbox
|
96
|
-
@ids
|
97
|
-
end
|
98
|
-
|
99
|
-
start = ids.index(cur_offset || start_offset) or raise OutOfSyncSourceError, "Unknown message id #{cur_offset || start_offset}."
|
100
|
-
end
|
90
|
+
def check; end # do nothing because anything we do will be too slow,
|
91
|
+
# and we'll catch the errors later.
|
101
92
|
|
102
93
|
## is this necessary? TODO: remove maybe
|
103
94
|
def == o; o.is_a?(IMAP) && o.uri == self.uri && o.username == self.username; end
|
@@ -109,6 +100,10 @@ class IMAP < Source
|
|
109
100
|
def load_message id
|
110
101
|
RMail::Parser.read raw_message(id)
|
111
102
|
end
|
103
|
+
|
104
|
+
def each_raw_message_line id
|
105
|
+
StringIO.new(raw_message(id)).each { |l| yield l }
|
106
|
+
end
|
112
107
|
|
113
108
|
def raw_header id
|
114
109
|
unsynchronized_scan_mailbox
|
@@ -164,13 +159,14 @@ class IMAP < Source
|
|
164
159
|
id = ids[i]
|
165
160
|
state = @mutex.synchronize { @imap_state[id] } or next
|
166
161
|
self.cur_offset = id
|
167
|
-
labels = { :
|
168
|
-
:Flagged => :starred,
|
162
|
+
labels = { :Flagged => :starred,
|
169
163
|
:Deleted => :deleted
|
170
164
|
}.inject(@labels) do |cur, (imap, sup)|
|
171
165
|
cur + (state[:flags].include?(imap) ? [sup] : [])
|
172
166
|
end
|
173
167
|
|
168
|
+
labels += [:unread] unless state[:flags].include?(:Seen)
|
169
|
+
|
174
170
|
yield id, labels
|
175
171
|
end
|
176
172
|
end
|
@@ -228,10 +224,12 @@ private
|
|
228
224
|
## fails with a NO response, the client may try another", in
|
229
225
|
## practice it seems like they can also send a BAD response.
|
230
226
|
begin
|
227
|
+
raise Net::IMAP::NoResponseError unless @imap.capability().member? "AUTH=CRAM-MD5"
|
231
228
|
@imap.authenticate 'CRAM-MD5', @username, @password
|
232
229
|
rescue Net::IMAP::BadResponseError, Net::IMAP::NoResponseError => e
|
233
230
|
Redwood::log "CRAM-MD5 authentication failed: #{e.class}. Trying LOGIN auth..."
|
234
231
|
begin
|
232
|
+
raise Net::IMAP::NoResponseError unless @imap.capability().member? "AUTH=LOGIN"
|
235
233
|
@imap.authenticate 'LOGIN', @username, @password
|
236
234
|
rescue Net::IMAP::BadResponseError, Net::IMAP::NoResponseError => e
|
237
235
|
Redwood::log "LOGIN authentication failed: #{e.class}. Trying plain-text LOGIN..."
|
data/lib/sup/index.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
## the index structure for redwood. interacts with ferret.
|
2
2
|
|
3
|
-
require 'thread'
|
4
3
|
require 'fileutils'
|
5
4
|
require 'ferret'
|
5
|
+
begin
|
6
|
+
require 'chronic'
|
7
|
+
$have_chronic = true
|
8
|
+
rescue LoadError => e
|
9
|
+
Redwood::log "optional 'chronic' library not found (run 'gem install chronic' to install)"
|
10
|
+
$have_chronic = false
|
11
|
+
end
|
6
12
|
|
7
13
|
module Redwood
|
8
14
|
|
@@ -18,6 +24,7 @@ class Index
|
|
18
24
|
include Singleton
|
19
25
|
|
20
26
|
attr_reader :index
|
27
|
+
alias ferret index
|
21
28
|
def initialize dir=BASE_DIR
|
22
29
|
@dir = dir
|
23
30
|
@sources = {}
|
@@ -46,7 +53,7 @@ class Index
|
|
46
53
|
end
|
47
54
|
|
48
55
|
def start_lock_update_thread
|
49
|
-
@lock_update_thread = Redwood::reporting_thread do
|
56
|
+
@lock_update_thread = Redwood::reporting_thread("lock update") do
|
50
57
|
while true
|
51
58
|
sleep 30
|
52
59
|
@lock.touch_yourself
|
@@ -112,8 +119,8 @@ EOS
|
|
112
119
|
def add_source source
|
113
120
|
raise "duplicate source!" if @sources.include? source
|
114
121
|
@sources_dirty = true
|
115
|
-
|
116
|
-
|
122
|
+
max = @sources.max_of { |id, s| s.is_a?(DraftLoader) || s.is_a?(SentLoader) ? 0 : id }
|
123
|
+
source.id ||= (max || 0) + 1
|
117
124
|
##source.id += 1 while @sources.member? source.id
|
118
125
|
@sources[source.id] = source
|
119
126
|
end
|
@@ -171,7 +178,7 @@ EOS
|
|
171
178
|
:date => m.date.to_indexable_s,
|
172
179
|
:body => m.content,
|
173
180
|
:snippet => m.snippet,
|
174
|
-
:label => m.labels.join(" "),
|
181
|
+
:label => m.labels.uniq.join(" "),
|
175
182
|
:from => m.from ? m.from.email : "",
|
176
183
|
:to => (m.to + m.cc + m.bcc).map { |x| x.email }.join(" "),
|
177
184
|
:subject => wrap_subj(Message.normalize_subj(m.subj)),
|
@@ -256,12 +263,14 @@ EOS
|
|
256
263
|
end
|
257
264
|
|
258
265
|
until pending.empty? || (opts[:limit] && messages.size >= opts[:limit])
|
259
|
-
id = pending.pop
|
260
|
-
next if searched.member? id
|
261
|
-
searched[id] = true
|
262
266
|
q = Ferret::Search::BooleanQuery.new true
|
263
|
-
|
264
|
-
|
267
|
+
|
268
|
+
pending.each do |id|
|
269
|
+
searched[id] = true
|
270
|
+
q.add_query Ferret::Search::TermQuery.new(:message_id, id), :should
|
271
|
+
q.add_query Ferret::Search::TermQuery.new(:refs, id), :should
|
272
|
+
end
|
273
|
+
pending = []
|
265
274
|
|
266
275
|
q = build_query :qobj => q
|
267
276
|
|
@@ -278,10 +287,11 @@ EOS
|
|
278
287
|
#Redwood::log "got #{mid} as a child of #{id}"
|
279
288
|
messages[mid] ||= lambda { build_message docid }
|
280
289
|
refs = @index[docid][:refs].split(" ")
|
281
|
-
pending += refs
|
290
|
+
pending += refs.select { |id| !searched[id] }
|
282
291
|
end
|
283
292
|
end
|
284
293
|
end
|
294
|
+
|
285
295
|
if killed
|
286
296
|
Redwood::log "thread for #{m.id} is killed, ignoring"
|
287
297
|
false
|
@@ -370,8 +380,10 @@ EOS
|
|
370
380
|
|
371
381
|
protected
|
372
382
|
|
383
|
+
## do any specialized parsing
|
384
|
+
## returns nil and flashes error message if parsing failed
|
373
385
|
def parse_user_query_string str
|
374
|
-
|
386
|
+
result = str.gsub(/\b(to|from):(\S+)\b/) do
|
375
387
|
field, name = $1, $2
|
376
388
|
if(p = ContactManager.contact_for(name))
|
377
389
|
[field, p.email]
|
@@ -380,8 +392,34 @@ protected
|
|
380
392
|
end.join(":")
|
381
393
|
end
|
382
394
|
|
383
|
-
|
384
|
-
|
395
|
+
if $have_chronic
|
396
|
+
chronic_failure = false
|
397
|
+
result = result.gsub(/\b(before|on|in|after):(\((.+?)\)\B|(\S+)\b)/) do
|
398
|
+
break if chronic_failure
|
399
|
+
field, datestr = $1, ($3 || $4)
|
400
|
+
realdate = Chronic.parse(datestr, :guess => false, :context => :none)
|
401
|
+
if realdate
|
402
|
+
case field
|
403
|
+
when "after"
|
404
|
+
Redwood::log "chronic: translated #{field}:#{datestr} to #{realdate.end}"
|
405
|
+
"date:(>= #{sprintf "%012d", realdate.end.to_i})"
|
406
|
+
when "before"
|
407
|
+
Redwood::log "chronic: translated #{field}:#{datestr} to #{realdate.begin}"
|
408
|
+
"date:(<= #{sprintf "%012d", realdate.begin.to_i})"
|
409
|
+
else
|
410
|
+
Redwood::log "chronic: translated #{field}:#{datestr} to #{realdate}"
|
411
|
+
"date:(<= #{sprintf "%012d", realdate.end.to_i}) date:(>= #{sprintf "%012d", realdate.begin.to_i})"
|
412
|
+
end
|
413
|
+
else
|
414
|
+
BufferManager.flash "Don't understand date #{datestr.inspect}!"
|
415
|
+
chronic_failure = true
|
416
|
+
end
|
417
|
+
end
|
418
|
+
result = nil if chronic_failure
|
419
|
+
end
|
420
|
+
|
421
|
+
Redwood::log "translated #{str.inspect} to #{result}" unless result == str
|
422
|
+
@qparser.parse result if result
|
385
423
|
end
|
386
424
|
|
387
425
|
def build_query opts
|