sup 0.1 → 0.2
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 +9 -0
- data/Manifest.txt +5 -1
- data/Rakefile +2 -1
- data/bin/sup +27 -10
- data/bin/sup-add +2 -1
- data/bin/sup-sync-back +51 -23
- data/doc/FAQ.txt +29 -37
- data/doc/Hooks.txt +38 -0
- data/doc/{UserGuide.txt → NewUserGuide.txt} +27 -21
- data/doc/TODO +91 -57
- data/lib/sup.rb +17 -1
- data/lib/sup/buffer.rb +80 -16
- data/lib/sup/colormap.rb +0 -2
- data/lib/sup/contact.rb +3 -2
- data/lib/sup/crypto.rb +110 -0
- data/lib/sup/draft.rb +2 -6
- data/lib/sup/hook.rb +131 -0
- data/lib/sup/imap.rb +27 -16
- data/lib/sup/index.rb +38 -14
- data/lib/sup/keymap.rb +0 -2
- data/lib/sup/label.rb +30 -9
- data/lib/sup/logger.rb +12 -1
- data/lib/sup/maildir.rb +48 -3
- data/lib/sup/mbox.rb +1 -1
- data/lib/sup/mbox/loader.rb +22 -12
- data/lib/sup/mbox/ssh-loader.rb +1 -1
- data/lib/sup/message-chunks.rb +198 -0
- data/lib/sup/message.rb +154 -115
- data/lib/sup/modes/compose-mode.rb +18 -0
- data/lib/sup/modes/contact-list-mode.rb +1 -1
- data/lib/sup/modes/edit-message-mode.rb +112 -31
- data/lib/sup/modes/file-browser-mode.rb +1 -1
- data/lib/sup/modes/inbox-mode.rb +1 -1
- data/lib/sup/modes/label-list-mode.rb +8 -6
- data/lib/sup/modes/label-search-results-mode.rb +4 -1
- data/lib/sup/modes/log-mode.rb +1 -1
- data/lib/sup/modes/reply-mode.rb +18 -16
- data/lib/sup/modes/search-results-mode.rb +1 -1
- data/lib/sup/modes/thread-index-mode.rb +61 -33
- data/lib/sup/modes/thread-view-mode.rb +111 -102
- data/lib/sup/person.rb +5 -1
- data/lib/sup/poll.rb +36 -7
- data/lib/sup/sent.rb +1 -0
- data/lib/sup/source.rb +7 -3
- data/lib/sup/textfield.rb +48 -34
- data/lib/sup/thread.rb +9 -5
- data/lib/sup/util.rb +16 -22
- metadata +7 -3
data/lib/sup/colormap.rb
CHANGED
data/lib/sup/contact.rb
CHANGED
@@ -10,7 +10,7 @@ class ContactManager
|
|
10
10
|
|
11
11
|
if File.exists? fn
|
12
12
|
IO.foreach(fn) do |l|
|
13
|
-
l =~ /^(
|
13
|
+
l =~ /^([^:]+): (.*)$/ or raise "can't parse #{fn} line #{l.inspect}"
|
14
14
|
aalias, addr = $1, $2
|
15
15
|
p = PersonManager.person_for addr, :definitive => true
|
16
16
|
@p2a[p] = aalias
|
@@ -35,9 +35,10 @@ class ContactManager
|
|
35
35
|
@a2p.delete aalias
|
36
36
|
end
|
37
37
|
end
|
38
|
-
def
|
38
|
+
def contact_for aalias; @a2p[aalias]; end
|
39
39
|
def alias_for person; @p2a[person]; end
|
40
40
|
def is_contact? person; @p2a.member? person; end
|
41
|
+
|
41
42
|
def save
|
42
43
|
File.open(@fn, "w") do |f|
|
43
44
|
@p2a.each do |p, a|
|
data/lib/sup/crypto.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class CryptoManager
|
4
|
+
include Singleton
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@mutex = Mutex.new
|
8
|
+
self.class.i_am_the_instance self
|
9
|
+
|
10
|
+
bin = `which gpg`.chomp
|
11
|
+
bin = `which pgp`.chomp unless bin =~ /\S/
|
12
|
+
|
13
|
+
@cmd =
|
14
|
+
case bin
|
15
|
+
when /\S/
|
16
|
+
"#{bin} --quiet --batch --no-verbose --logger-fd 1 --use-agent"
|
17
|
+
else
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# returns a cryptosignature
|
23
|
+
def verify payload, signature # both RubyMail::Message objects
|
24
|
+
return unknown_status(cant_find_binary) unless @cmd
|
25
|
+
|
26
|
+
payload_fn = Tempfile.new "redwood.payload"
|
27
|
+
payload_fn.write payload.to_s.gsub(/(^|[^\r])\n/, "\\1\r\n").gsub(/^MIME-Version: .*\r\n/, "")
|
28
|
+
payload_fn.close
|
29
|
+
|
30
|
+
signature_fn = Tempfile.new "redwood.signature"
|
31
|
+
signature_fn.write signature.decode
|
32
|
+
signature_fn.close
|
33
|
+
|
34
|
+
cmd = "#{@cmd} --verify #{signature_fn.path} #{payload_fn.path} 2> /dev/null"
|
35
|
+
|
36
|
+
#Redwood::log "gpg: running: #{cmd}"
|
37
|
+
gpg_output = `#{cmd}`
|
38
|
+
#Redwood::log "got output: #{gpg_output.inspect}"
|
39
|
+
output_lines = gpg_output.split(/\n/)
|
40
|
+
|
41
|
+
if gpg_output =~ /^gpg: (.* signature from .*$)/
|
42
|
+
if $? == 0
|
43
|
+
Chunk::CryptoNotice.new :valid, $1, output_lines
|
44
|
+
else
|
45
|
+
Chunk::CryptoNotice.new :invalid, $1, output_lines
|
46
|
+
end
|
47
|
+
else
|
48
|
+
unknown_status output_lines
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# returns decrypted_message, status, desc, lines
|
53
|
+
def decrypt payload # RubyMail::Message objects
|
54
|
+
return unknown_status(cant_find_binary) unless @cmd
|
55
|
+
|
56
|
+
# cmd = "#{@cmd} --decrypt 2> /dev/null"
|
57
|
+
|
58
|
+
# Redwood::log "gpg: running: #{cmd}"
|
59
|
+
|
60
|
+
# gpg_output =
|
61
|
+
# IO.popen(cmd, "a+") do |f|
|
62
|
+
# f.puts payload.to_s
|
63
|
+
# f.gets
|
64
|
+
# end
|
65
|
+
|
66
|
+
payload_fn = Tempfile.new "redwood.payload"
|
67
|
+
payload_fn.write payload.to_s
|
68
|
+
payload_fn.close
|
69
|
+
|
70
|
+
cmd = "#{@cmd} --decrypt #{payload_fn.path} 2> /dev/null"
|
71
|
+
Redwood::log "gpg: running: #{cmd}"
|
72
|
+
gpg_output = `#{cmd}`
|
73
|
+
Redwood::log "got output: #{gpg_output.inspect}"
|
74
|
+
|
75
|
+
if $? == 0 # successful decryption
|
76
|
+
decrypted_payload, sig_lines =
|
77
|
+
if gpg_output =~ /\A(.*?)((^gpg: .*$)+)\Z/m
|
78
|
+
[$1, $2]
|
79
|
+
else
|
80
|
+
[gpg_output, nil]
|
81
|
+
end
|
82
|
+
|
83
|
+
sig =
|
84
|
+
if sig_lines # encrypted & signed
|
85
|
+
if sig_lines =~ /^gpg: (Good signature from .*$)/
|
86
|
+
Chunk::CryptoNotice.new :valid, $1, sig_lines.split("\n")
|
87
|
+
else
|
88
|
+
Chunk::CryptoNotice.new :invalid, $1, sig_lines.split("\n")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
notice = Chunk::CryptoNotice.new :valid, "This message has been decrypted for display"
|
93
|
+
[RMail::Parser.read(decrypted_payload), sig, notice]
|
94
|
+
else
|
95
|
+
notice = Chunk::CryptoNotice.new :invalid, "This message could not be decrypted", gpg_output.split("\n")
|
96
|
+
[nil, nil, notice]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def unknown_status lines=[]
|
103
|
+
Chunk::CryptoNotice.new :unknown, "Unable to determine validity of cryptographic signature", lines
|
104
|
+
end
|
105
|
+
|
106
|
+
def cant_find_binary
|
107
|
+
["Can't find gpg or pgp binary in path"]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/sup/draft.rb
CHANGED
@@ -99,12 +99,8 @@ class DraftLoader < Source
|
|
99
99
|
ret
|
100
100
|
end
|
101
101
|
|
102
|
-
def
|
103
|
-
|
104
|
-
File.open fn_for_offset(offset) do |f|
|
105
|
-
ret += l until f.eof?
|
106
|
-
end
|
107
|
-
ret
|
102
|
+
def raw_message offset
|
103
|
+
IO.readlines(fn_for_offset(offset)).join
|
108
104
|
end
|
109
105
|
|
110
106
|
def start_offset; 0; end
|
data/lib/sup/hook.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class HookManager
|
4
|
+
## there's probably a better way to do this, but to evaluate a hook
|
5
|
+
## with a bunch of pre-set "local variables" i define a function
|
6
|
+
## per variable and then instance_evaluate the code.
|
7
|
+
##
|
8
|
+
## how does rails do it, when you pass :locals into a partial?
|
9
|
+
##
|
10
|
+
## i don't bother providing setters, since i'm pretty sure the
|
11
|
+
## charade will fall apart pretty quickly with respect to scoping.
|
12
|
+
## this is basically fail-fast.
|
13
|
+
class HookContext
|
14
|
+
def initialize name
|
15
|
+
@__name = name
|
16
|
+
@__locals = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_writer :__locals
|
20
|
+
|
21
|
+
def method_missing m, *a
|
22
|
+
case @__locals[m]
|
23
|
+
when Proc
|
24
|
+
@__locals[m].call(*a)
|
25
|
+
when nil
|
26
|
+
super
|
27
|
+
else
|
28
|
+
@__locals[m]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def say s
|
33
|
+
@__say_id = BufferManager.say s, @__say_id
|
34
|
+
BufferManager.draw_screen
|
35
|
+
end
|
36
|
+
|
37
|
+
def log s
|
38
|
+
Redwood::log "hook[#@__name]: #{s}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def ask_yes_or_no q
|
42
|
+
BufferManager.ask_yes_or_no q
|
43
|
+
end
|
44
|
+
|
45
|
+
def __binding
|
46
|
+
binding
|
47
|
+
end
|
48
|
+
|
49
|
+
def __cleanup
|
50
|
+
BufferManager.clear @__say_id if @__say_id
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
include Singleton
|
55
|
+
|
56
|
+
def initialize dir
|
57
|
+
@dir = dir
|
58
|
+
@hooks = {}
|
59
|
+
@descs = {}
|
60
|
+
@contexts = {}
|
61
|
+
|
62
|
+
Dir.mkdir dir unless File.exists? dir
|
63
|
+
|
64
|
+
self.class.i_am_the_instance self
|
65
|
+
end
|
66
|
+
|
67
|
+
def run name, locals={}
|
68
|
+
hook = hook_for(name) or return
|
69
|
+
context = @contexts[hook] ||= HookContext.new(name)
|
70
|
+
context.__locals = locals
|
71
|
+
|
72
|
+
result = nil
|
73
|
+
begin
|
74
|
+
result = context.instance_eval @hooks[name], fn_for(name)
|
75
|
+
rescue Exception => e
|
76
|
+
log "error running hook: #{e.message}"
|
77
|
+
log e.backtrace.join("\n")
|
78
|
+
BufferManager.flash "Error running hook: #{e.message}"
|
79
|
+
end
|
80
|
+
context.__cleanup
|
81
|
+
result
|
82
|
+
end
|
83
|
+
|
84
|
+
def register name, desc
|
85
|
+
@descs[name] = desc
|
86
|
+
end
|
87
|
+
|
88
|
+
def print_hooks f=$stdout
|
89
|
+
puts <<EOS
|
90
|
+
Have #{@descs.size} registered hooks:
|
91
|
+
|
92
|
+
EOS
|
93
|
+
|
94
|
+
@descs.sort.each do |name, desc|
|
95
|
+
f.puts <<EOS
|
96
|
+
#{name}
|
97
|
+
#{"-" * name.length}
|
98
|
+
File: #{fn_for name}
|
99
|
+
#{desc}
|
100
|
+
EOS
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def hook_for name
|
107
|
+
unless @hooks.member? name
|
108
|
+
@hooks[name] =
|
109
|
+
begin
|
110
|
+
returning IO.readlines(fn_for(name)).join do
|
111
|
+
log "read '#{name}' from #{fn_for(name)}"
|
112
|
+
end
|
113
|
+
rescue SystemCallError => e
|
114
|
+
#log "disabled hook for '#{name}': #{e.message}"
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
@hooks[name]
|
120
|
+
end
|
121
|
+
|
122
|
+
def fn_for name
|
123
|
+
File.join @dir, "#{name}.rb"
|
124
|
+
end
|
125
|
+
|
126
|
+
def log m
|
127
|
+
Redwood::log("hook: " + m)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
data/lib/sup/imap.rb
CHANGED
@@ -3,6 +3,7 @@ require 'net/imap'
|
|
3
3
|
require 'stringio'
|
4
4
|
require 'time'
|
5
5
|
require 'rmail'
|
6
|
+
require 'cgi'
|
6
7
|
|
7
8
|
## fucking imap fucking sucks. what the FUCK kind of committee of
|
8
9
|
## dunces designed this shit.
|
@@ -62,10 +63,10 @@ class IMAP < Source
|
|
62
63
|
@username = username
|
63
64
|
@password = password
|
64
65
|
@imap = nil
|
65
|
-
@
|
66
|
+
@imap_state = {}
|
66
67
|
@ids = []
|
67
68
|
@last_scan = nil
|
68
|
-
@labels = (labels || []).freeze
|
69
|
+
@labels = ((labels || []) - LabelManager::RESERVED_LABELS).uniq.freeze
|
69
70
|
@say_id = nil
|
70
71
|
@mutex = Mutex.new
|
71
72
|
end
|
@@ -82,11 +83,13 @@ class IMAP < Source
|
|
82
83
|
def port; @parsed_uri.port || (ssl? ? 993 : 143); end
|
83
84
|
def mailbox
|
84
85
|
x = @parsed_uri.path[1..-1]
|
85
|
-
x.nil? || x.empty? ? 'INBOX' : x
|
86
|
+
(x.nil? || x.empty?) ? 'INBOX' : CGI.unescape(x)
|
86
87
|
end
|
87
88
|
def ssl?; @parsed_uri.scheme == 'imaps' end
|
88
89
|
|
89
90
|
def check
|
91
|
+
return unless start_offset
|
92
|
+
|
90
93
|
ids =
|
91
94
|
@mutex.synchronize do
|
92
95
|
unsynchronized_scan_mailbox
|
@@ -104,23 +107,21 @@ class IMAP < Source
|
|
104
107
|
end
|
105
108
|
|
106
109
|
def load_message id
|
107
|
-
RMail::Parser.read
|
110
|
+
RMail::Parser.read raw_message(id)
|
108
111
|
end
|
109
112
|
|
110
113
|
def raw_header id
|
111
114
|
unsynchronized_scan_mailbox
|
112
|
-
header, flags = get_imap_fields id, 'RFC822.HEADER'
|
113
|
-
## very bad. this is very very bad. very bad bad bad.
|
114
|
-
header = header + "Status: RO\n" if flags.include? :Seen # fake an mbox-style read header # TODO: improve source-marked-as-read reporting system
|
115
|
+
header, flags = get_imap_fields id, 'RFC822.HEADER'
|
115
116
|
header.gsub(/\r\n/, "\n")
|
116
117
|
end
|
117
118
|
synchronized :raw_header
|
118
119
|
|
119
|
-
def
|
120
|
+
def raw_message id
|
120
121
|
unsynchronized_scan_mailbox
|
121
122
|
get_imap_fields(id, 'RFC822').first.gsub(/\r\n/, "\n")
|
122
123
|
end
|
123
|
-
synchronized :
|
124
|
+
synchronized :raw_message
|
124
125
|
|
125
126
|
def connect
|
126
127
|
return if @imap
|
@@ -140,15 +141,17 @@ class IMAP < Source
|
|
140
141
|
|
141
142
|
range = (@ids.length + 1) .. last_id
|
142
143
|
Redwood::log "fetching IMAP headers #{range}"
|
143
|
-
fetch(range, ['RFC822.SIZE', 'INTERNALDATE']).each do |v|
|
144
|
+
fetch(range, ['RFC822.SIZE', 'INTERNALDATE', 'FLAGS']).each do |v|
|
144
145
|
id = make_id v
|
145
146
|
@ids << id
|
146
|
-
@
|
147
|
+
@imap_state[id] = { :id => v.seqno, :flags => v.attr["FLAGS"] }
|
147
148
|
end
|
148
149
|
end
|
149
150
|
synchronized :scan_mailbox
|
150
151
|
|
151
152
|
def each
|
153
|
+
return unless start_offset
|
154
|
+
|
152
155
|
ids =
|
153
156
|
@mutex.synchronize do
|
154
157
|
unsynchronized_scan_mailbox
|
@@ -157,10 +160,18 @@ class IMAP < Source
|
|
157
160
|
|
158
161
|
start = ids.index(cur_offset || start_offset) or raise OutOfSyncSourceError, "Unknown message id #{cur_offset || start_offset}."
|
159
162
|
|
160
|
-
start.upto(ids.length - 1) do |i|
|
163
|
+
start.upto(ids.length - 1) do |i|
|
161
164
|
id = ids[i]
|
162
|
-
|
163
|
-
|
165
|
+
state = @mutex.synchronize { @imap_state[id] } or next
|
166
|
+
self.cur_offset = id
|
167
|
+
labels = { :Seen => :unread,
|
168
|
+
:Flagged => :starred,
|
169
|
+
:Deleted => :deleted
|
170
|
+
}.inject(@labels) do |cur, (imap, sup)|
|
171
|
+
cur + (state[:flags].include?(imap) ? [sup] : [])
|
172
|
+
end
|
173
|
+
|
174
|
+
yield id, labels
|
164
175
|
end
|
165
176
|
end
|
166
177
|
|
@@ -259,9 +270,9 @@ private
|
|
259
270
|
end
|
260
271
|
|
261
272
|
def get_imap_fields id, *fields
|
262
|
-
|
273
|
+
raise OutOfSyncSourceError, "Unknown message id #{id}" unless @imap_state[id]
|
263
274
|
|
264
|
-
|
275
|
+
imap_id = @imap_state[id][:id]
|
265
276
|
result = fetch(imap_id, (fields + ['RFC822.SIZE', 'INTERNALDATE']).uniq).first
|
266
277
|
got_id = make_id result
|
267
278
|
raise OutOfSyncSourceError, "IMAP message mismatch: requested #{id}, got #{got_id}." unless got_id == id
|
data/lib/sup/index.rb
CHANGED
@@ -154,11 +154,10 @@ EOS
|
|
154
154
|
docid, entry = load_entry_for_id m.id unless docid && entry
|
155
155
|
|
156
156
|
raise "no source info for message #{m.id}" unless m.source && m.source_info
|
157
|
-
raise "trying
|
157
|
+
raise "trying to delete non-corresponding entry #{docid} with index message-id #{@index[docid][:message_id].inspect} and parameter message id #{m.id.inspect}" if docid && @index[docid][:message_id] != m.id
|
158
158
|
|
159
159
|
source_id =
|
160
160
|
if m.source.is_a? Integer
|
161
|
-
raise "Debugging: integer source set"
|
162
161
|
m.source
|
163
162
|
else
|
164
163
|
m.source.id or raise "unregistered source #{m.source} (id #{m.source.id.inspect})"
|
@@ -184,7 +183,7 @@ EOS
|
|
184
183
|
|
185
184
|
docid, entry = load_entry_for_id m.id
|
186
185
|
## this hasn't been triggered in a long time. TODO: decide whether it's still a problem.
|
187
|
-
raise "just added message #{m.id} but couldn't find it in a search" unless docid
|
186
|
+
raise "just added message #{m.id.inspect} but couldn't find it in a search" unless docid
|
188
187
|
true
|
189
188
|
end
|
190
189
|
|
@@ -226,10 +225,12 @@ EOS
|
|
226
225
|
## message-building lambdas, so that building an unwanted message
|
227
226
|
## can be skipped in the block if desired.
|
228
227
|
##
|
229
|
-
##
|
228
|
+
## only two options, :limit and :skip_killed. if :skip_killed is
|
229
|
+
## true, stops loading any thread if a message with a :killed flag
|
230
|
+
## is found.
|
230
231
|
SAME_SUBJECT_DATE_LIMIT = 7
|
231
232
|
def each_message_in_thread_for m, opts={}
|
232
|
-
Redwood::log "Building thread for #{m.id}: #{m.subj}"
|
233
|
+
#Redwood::log "Building thread for #{m.id}: #{m.subj}"
|
233
234
|
messages = {}
|
234
235
|
searched = {}
|
235
236
|
num_queries = 0
|
@@ -262,23 +263,33 @@ EOS
|
|
262
263
|
q.add_query Ferret::Search::TermQuery.new(:message_id, id), :should
|
263
264
|
q.add_query Ferret::Search::TermQuery.new(:refs, id), :should
|
264
265
|
|
265
|
-
q = build_query :qobj => q
|
266
|
+
q = build_query :qobj => q
|
266
267
|
|
267
268
|
num_queries += 1
|
269
|
+
killed = false
|
268
270
|
@index.search_each(q, :limit => :all) do |docid, score|
|
269
271
|
break if opts[:limit] && messages.size >= opts[:limit]
|
270
|
-
|
272
|
+
if @index[docid][:label].split(/\s+/).include?("killed") && opts[:skip_killed]
|
273
|
+
killed = true
|
274
|
+
break
|
275
|
+
end
|
271
276
|
mid = @index[docid][:message_id]
|
272
277
|
unless messages.member?(mid)
|
273
|
-
Redwood::log "got #{mid} as a child of #{id}"
|
278
|
+
#Redwood::log "got #{mid} as a child of #{id}"
|
274
279
|
messages[mid] ||= lambda { build_message docid }
|
275
280
|
refs = @index[docid][:refs].split(" ")
|
276
281
|
pending += refs
|
277
282
|
end
|
278
283
|
end
|
279
284
|
end
|
280
|
-
|
281
|
-
|
285
|
+
if killed
|
286
|
+
Redwood::log "thread for #{m.id} is killed, ignoring"
|
287
|
+
false
|
288
|
+
else
|
289
|
+
Redwood::log "ran #{num_queries} queries to build thread of #{messages.size + 1} messages for #{m.id}: #{m.subj}" if num_queries > 0
|
290
|
+
messages.each { |mid, builder| yield mid, builder }
|
291
|
+
true
|
292
|
+
end
|
282
293
|
end
|
283
294
|
|
284
295
|
## builds a message object from a ferret result
|
@@ -294,7 +305,7 @@ EOS
|
|
294
305
|
"from" => doc[:from],
|
295
306
|
"to" => doc[:to],
|
296
307
|
"message-id" => doc[:message_id],
|
297
|
-
"references" => doc[:refs],
|
308
|
+
"references" => doc[:refs].split(/\s+/).map { |x| "<#{x}>" }.join(" "),
|
298
309
|
}
|
299
310
|
|
300
311
|
Message.new :source => source, :source_info => doc[:source_info].to_i,
|
@@ -330,7 +341,7 @@ EOS
|
|
330
341
|
num = h[:num] || 20
|
331
342
|
@index.search_each(q, :sort => "date DESC", :limit => :all) do |docid, score|
|
332
343
|
break if contacts.size >= num
|
333
|
-
#Redwood::log "got message
|
344
|
+
#Redwood::log "got message #{docid} to: #{@index[docid][:to].inspect} and from: #{@index[docid][:from].inspect}"
|
334
345
|
f = @index[docid][:from]
|
335
346
|
t = @index[docid][:to]
|
336
347
|
|
@@ -359,7 +370,20 @@ EOS
|
|
359
370
|
|
360
371
|
protected
|
361
372
|
|
362
|
-
def parse_user_query_string str
|
373
|
+
def parse_user_query_string str
|
374
|
+
str2 = str.gsub(/(to|from):(\S+)/) do
|
375
|
+
field, name = $1, $2
|
376
|
+
if(p = ContactManager.contact_for(name))
|
377
|
+
[field, p.email]
|
378
|
+
else
|
379
|
+
[field, name]
|
380
|
+
end.join(":")
|
381
|
+
end
|
382
|
+
|
383
|
+
Redwood::log "translated #{str} to #{str2}" unless str2 == str
|
384
|
+
@qparser.parse str2
|
385
|
+
end
|
386
|
+
|
363
387
|
def build_query opts
|
364
388
|
query = Ferret::Search::BooleanQuery.new
|
365
389
|
query.add_query opts[:qobj], :must if opts[:qobj]
|
@@ -376,7 +400,7 @@ protected
|
|
376
400
|
|
377
401
|
query.add_query Ferret::Search::TermQuery.new("label", "spam"), :must_not unless opts[:load_spam] || labels.include?(:spam)
|
378
402
|
query.add_query Ferret::Search::TermQuery.new("label", "deleted"), :must_not unless opts[:load_deleted] || labels.include?(:deleted)
|
379
|
-
query.add_query Ferret::Search::TermQuery.new("label", "killed"), :must_not
|
403
|
+
query.add_query Ferret::Search::TermQuery.new("label", "killed"), :must_not if opts[:skip_killed]
|
380
404
|
query
|
381
405
|
end
|
382
406
|
|